zoukankan      html  css  js  c++  java
  • 菜鸡的Java笔记 第三十七

    线程与进程
            线程与进程的区别
            
            最早的的时候DOS 系统有一个特点:只要电脑有病毒,那么电脑就死机了,是因为传统的DOS 系统属于单进程的操作系统
            即:在同一个时间段内只允许有一个程序运行。
            而后来到了window 时代发生了改变,电脑即使有病毒了也可以照常使用,但是会变慢
            因为在一个CPU ,一块资源的情况下,程序利用一些轮转算法,可以让一个资源在一个时间段上可以同时处理多个不同的程序(进程),但是i在一个时间点上只允许有一个进程去执行
            在每个进程上可以继续划分出若干个线程,那么线程的操作一定是要比进程更快的,所以多线程的操作性能一定要超过多进程的操作
            但是所有的线程都一定是要在进程的基础上进行划分,所以进程一旦消失,那么线程也会消失
            
        总结
            线程永远要依附于进程存在

    */

    /*    多线程的实现(继承 Thread 类实现)
            实现javac的多线程操作
            
            在java中对于多线程实现一定要有一个线程的主类,而这个线程的主类往往是需要操作一些资源
            但是对于这个多线程主类的实现是有一定的要求:
                继承 Thread 父类
                实现 Runnable 接口( Callable 接口)
        
        继承 Thread 类实现多线程
            在 java.lang 包中存在有 Thread 类。子类继承 Thread 类之后需要覆写 Thread 类中的 run() 方法
            那么这个方法就属于线程的主方法,定义: public void run()
            
            范例:实现线程的主体类

    class MyThread extends Thread{
        private String name;
        public  MyThread(String name) {
            // TODO Auto-generated constructor stub
            this.name = name;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 10; i++) {
                System.out.println(this.name + ",i = "+i);
            }
        }
    }

               
                在线程的主类之中只是将内容输出10次
            但是需要注意的是:所有的多线程的执行一定是i并发完成的,即:在同一个时间段会有多个线程交替执行
            所以为了达到这样的目的,绝对不能够直接去调用 run() 方法,而是应该调用 Thread 类中的 start() 方法启动多线程:public void start()
            
            范例:启动多线程

    MyThread mt1 = new MyThread("线程A");
    MyThread mt2 = new MyThread("线程B");
    MyThread mt3 = new MyThread("线程C");
    mt1.start();
    mt2.start();
    mt3.start();

               
                所有的线程都属于交替执行,本身是没有固定的执行顺序的
                
            思考:为什么现在启动多线程不使用 run() 方法。而非要使用 start() 方法?
                为了方便解释此问题,,必须打开 Thread 类中的 start() 源代码来观察

    public synchronized void start() {
    
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }
    
    private native void start0();

               
                现在的代码之中首先可以发现方法会抛出一个异常: IllegalThreadStateException
                但是整个方法里面没有使用 throws 声明,没有 try...catch 捕获处理,而之所以会出现这样的情况是因为此异常属于 RuntimeException 的子类
                java.lang.Object
                    java.lang.Throwable
                        java.lang.Exception
                            java.lang.RuntimeException
                                java.lang.IllegalArgumentException
                                    java.lang.IllegalThreadStateException
                                    
                此异常指的是一个线程已经调用了 start() 方法后uyou重复执行了 start() 方法所造成的问题
                在调用 start() 方法里面发现会调用 start0() 方法,而 start0() 方法上使用了 native 关键字定义,这个关键字指的是要调用本机的操作系统函数
                由于线程的启动需要牵扯到操作系统中的资源分配问题,所以具体的线程的启动应该要根据不同的操作系统有不同的实现
                而JVM相当于根据系统中定义的 start0() 方法来个根据不同的操作系统进行该方法的实现,这样在多线程的层次上 start0() 方法名称不改变
                而不同的操作系统上有不同的实现
                
                结论:只有 Thread 类的 start() 方法才能进行操作系统的资源的分配,所以启动多线程的方式永远就是调用 Thread 类的 start() 方法实现

    package cn.mysterious.study3;
    
    class MyThread extends Thread{
        private String name;
        public  MyThread(String name) {
            // TODO Auto-generated constructor stub
            this.name = name;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 10; i++) {
                System.out.println(this.name + ",i = "+i);
            }
        }
    }
    
    public class StudyThread {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            MyThread mt1 = new MyThread("线程A");
            MyThread mt2 = new MyThread("线程B");
            MyThread mt3 = new MyThread("线程C");
            mt1.start();
            mt2.start();
            mt3.start();
            // 下面是 现执行A 后B 再C
            mt1.run();
            mt2.run();
            mt3.run();
        }
    
    }

                
        实现 Runnable 接口
            继承 Thread 类会产生单继承的局限操作,所以现在最好的做法是利用接口来解决问题,于是就可以使用 Runnable 接口来完成操作
            首先来观察一下 Runnable 接口的定义结构:

    @FunctionalInterface
    public interface Runnable{
        public void run();
    }

               
            此时的代码使用的是函数式的接口。可以利用 Lamda 表达式完成
            
            范例:按照正常思路实现多线程

    class MyThread implements Runnable {
        private String name;
        public  MyThread(String name) {
            // TODO Auto-generated constructor stub
            this.name = name;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 10; i++) {
                System.out.println(this.name + ",i = "+i);
            }
        }
    }

               
            如果要想启动多线程依靠只能够是 Thread 类中的 start() 方法,在之前继承 Thread 类的时候可以直接将 start() 方法继承下来继承使用
            但是现在实现的是 Runnable 接口,所以此方法没有了
            于是来观察 Thread 类中的构造方法: public Thread(Runnable target)

    // TODO Auto-generated method stub
    MyThread mt1 = new MyThread("线程A");
    MyThread mt2 = new MyThread("线程B");
    MyThread mt3 = new MyThread("线程C");
    new Thread(mt1).start();
    new Thread(mt2).start();
    new Thread(mt3).start();

               
            很多时候为了方便实现,可能直接使用匿名内部类或者是 Lamda 实现代码
            
            范例:观察实现

    public class StudyThread {
    
        public static void main(String[] args) {
            String name = "?????";
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name + ",i = "+i);
                    }
                }
            }).start();
        }
    
    }

               
            范例:JDK1.8使用 Lamda

    public class StudyThread {
    
        public static void main(String[] args) {
            String name = "?????";
            new Thread(()->{
    
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name + ",i = "+i);
                    }
                }
            ).start();
        }
    
    }

               
            只要给出的是函数式接口基本上就都可以使用 Lamda 表达式或者是方法引用
            
        两种实现方式的区别(面试题)
            对于多线程的两种实现模式:继承 Thread 类,实现 Runnable 接口,那么这两种模式本质上来讲,一定使用 Runnable接口实现
            这样可以避免单继承局限,但是除了这样的使用原则之外,还需要清楚这两种实现方式的联系
            首先观察 Thread 类的定义结构:    
                public class Thread extends Object implements Runnable
            可以发现 Thread 类实现了 Runnable接口
            通过分析可以发现,整个代码的操作中使用的就是一个代理设计模式的结构,但是与传统的代理设计还有些差别
            如果按照传统的代理设计模式来讲,现在如果要想启动多线程理论应该是 run() 方法,但是实质上现在调用的是 start() 名称不符合
            之所以会这样主要是因为长期发展后的产物,最早的时候设计模式就是个梦
            除了以上的继承关联之外还有一点区别: Runnable 接口实现的多线程要比 Thread 类实现的多线程更方便的表示出数据共享的概念
            
            范例:希望有三个线程进行卖票 -- Thread 实现

    package cn.mysterious.study3;
    
    class MyThread extends Thread {
        private int ticket = 5;
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 50; i++) {
                if (this.ticket > 0) {
                    System.out.println("卖票,ticket = "+ this.ticket --);
                }
            }
        }
    }
    
    public class StudyThread {
    
        public static void main(String[] args) {
            MyThread mt1 = new MyThread();
            MyThread mt2 = new MyThread();
            MyThread mt3 = new MyThread();
            mt1.start();
            mt2.start();
            mt3.start();
        }
    
    }
    /*
    结果:
    卖票,ticket = 5
    卖票,ticket = 4
    卖票,ticket = 3
    卖票,ticket = 2
    卖票,ticket = 1
    卖票,ticket = 5
    卖票,ticket = 4
    卖票,ticket = 3
    卖票,ticket = 2
    卖票,ticket = 1
    卖票,ticket = 5
    卖票,ticket = 4
    卖票,ticket = 3
    卖票,ticket = 2
    卖票,ticket = 1


    */
                
                发现现在的三个线程各自都在卖着各自的票
                
            范例:使用 Runnable 接口来实现多线程

    package cn.mysterious.study3;
    
    class MyThread implements Runnable {
        private int ticket = 5;
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 50; i++) {
                if (this.ticket > 0) {
                    System.out.println("卖票,ticket = "+ this.ticket --);
                }
            }
        }
    }
    
    public class StudyThread {
    
        public static void main(String[] args) {
            MyThread mt = new MyThread();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
    
        }
    
    }

               
            面试题:请解释多线程的两种实现方式以及区别?请分别用代码验证
                多线程需要一个线程的主类,这个类要么继承 Thread 类,要么实现 Runnable 接口
                使用 Runnable 接口可以比 Thread 类更好的实现数据共享的操作,并且利用 Runnable 接口可以避免单继承局限问题

        实现 Callable 接口
            从JDK1.5之后对于多线程的实现多了一个 Callable 接口,在这个接口里面比 Runnable 接口唯一的强大之处在于它可以返回执行结果
            此接口定义在 java.util.concurrent 包中        

                @FunctionalInterface
                public interface Callable<V>{
                    public V call() throws Exception
                }

               
            这个泛型表示的是返回值类型。call() 方法就相当于 run() 方法
            
            范例:定义线程的主题类

    class MyThread implements Callable<String> {
        private int ticket = 5;
        @Override
        public String call() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 50; i++) {
                if (this.ticket > 0) {
                    System.out.println("卖票,ticket = "+ this.ticket --);
                }
            }
            return "票卖完了";
        }
        
    }

               
                但是现在出现了一个问题, Thread 类中并没有提供接收 Callable 接口的对象操作
                所现在如何启动多线程就出现了问题。为了分析出启动的操作,需要来观察继承结构
                    首先来观察 java.util.concurrent Class FutureTask<V> 类的定义结构
                    

    public class StudyThread {
    
        public static void main(String[] args) throws Exception {
            Callable<String> cal = new MyThread();
            FutureTask<String> task = new FutureTask<>(cal); // 取得执行结果
            Thread thread = new Thread(task);
            thread.start();
            System.out.println(task.get()); // 取得线程主方法的返回值
        }
    
    }

                   
                对于线程的第三种实现方式没有特别的要求
                
        总结
            Thread 有单继承局限所以不使用,但是所有的线程对象一定要通过 Thread 里中的 start() 方法启动
            Runnable 使用是 可以避免单继承局限,所以建议使用此操作
            Callable 比 Runnable 唯一的好处是多了返回值的数据
                
               

  • 相关阅读:
    make 实例 一 3463
    python3 中对arrow库的总结(转发)
    impala 导出CSV 或excel
    设置虚拟机IP
    centos7 tomcat9
    eclipse 创建普通maven项目
    java log4j日志配置
    java运行jar命令提示没有主清单属性
    Java 读取 .properties 配置文件
    python 机器学习多项式回归
  • 原文地址:https://www.cnblogs.com/mysterious-killer/p/10123663.html
Copyright © 2011-2022 走看看