美文网首页
Java线程间通信的原理和机制

Java线程间通信的原理和机制

作者: 如愿以偿丶 | 来源:发表于2020-11-12 17:46 被阅读0次

    1.线程间通信

    1.1.thread.interrupt()

      我们理解的可能就是线程和线程之间对话。

      最简单的线程间通信就是一个线程启动另一个线程:
      问题1:
        如何做到一个线程终结另一个线程呢?
      解答1:
        我们最早使用的stop()方法,直接结束线程。但是方法过时了,不建议使用
      问题2:
        这么好用为什么弃用了呢?
      解答2:
        因为他会立刻终止线程,它的结果是不可预期的。比如我要改修改5条数据,改了3条就给我切断了

    public class ThreadInteractionDemo implements TestDemo{
        
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 1_000_000; i++) {
                        System.out.println("number:"+i);
                    }
                }
            };
            thread.start();  //启动线程
            thread.stop();  //把这个线程立即掐断,立刻就结束了。结果什么都不打印
        }
    }
    

      问题3:
        那我们有什么好的方法呢?
      解答3:
        我们可以使用thread.interrupt();,进行处理,他的意思是中断,但是他只是一个标记,只是将当前线程标记为中断状态,或者说把他的中断状态设置为true,但是他是需要目标(也就是设置interrupt()的线程)线程进行配合,相当于我发了一个通知我需要你结束,目标线程接到通知后进行终止线程。

    public class ThreadInteractionDemo implements TestDemo{
     
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 1_000_000; i++) {
                        //需要配合,进行判断,
                        //Thread.interrupted()和isInterrupted()都可以
                        if (Thread.interrupted()){
                            //线程马上结束了,进行收尾工作
                            return;
                        }
                        System.out.println("number:"+i);
                    }
                }
            };
            thread.start();
            /**
             * thread.interrupt();
             * 他只是一个标记。其实只是将这个线程标记为中断状态。或者说把它的中断状态设置为true,他需要你的目标线程配合这件事情,例如给个通知,我需要你结束了,是一个温和版本的,暴力版本的stop被弃用了。
             *      1.他不是立刻的
             *      2.他不是强制的
             *      3.你想终止就终止,我不管你,我只是一个通知。需要自己来支持的,通过isInterrupt()或者Thread.interrupted()来判断,Thread.interrupted()调用后会重置状态,我们需要自己做收尾工作,知道线程马上结束了。
             */
            thread.interrupt();  
        }
    }
    

      Thread.interrupted()和isInterrupted()的区别
        1.Thread.interrupted()调用后会将interrupt(中断状态设置为false),注意当执行InterruptedException e的时候也会将状态重置。
        2.isInterrupted()始终都是对应的值,不会重置状态

    public class ThreadInteractionDemo implements TestDemo{
        
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 1_000_000; i++) {
                        if (Thread.interrupted()){
                            return;
                        }
                        try {
                            //打断这里睡1000毫秒(醒醒别睡了,我要中断你了。此时就会抛出异常)
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            //当抛出异常时,会将interrupt的状态重置,重新置为false。
                            e.printStackTrace();
                        }
                        System.out.println("number:"+i);
                    }
                }
            };
            thread.start();
            try {
                //睡100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
        }
    }
    

      打印结果:

    interrupt打断.gif
      结论:
        通过打印结果发现,抛出异常后,还是会执行打印,每隔1秒打印一次。为什么我们都中断了还会打印呢,就是因为InterruptedException抛出异常后,会将打断(interrupt)置为false,之后for循环里Thread.interrupted()始终都是false,就像是没有人调用过一样。所以就会每隔一秒执行一次。我们只需要在抛出异常地方也做收尾工作就可以了。
    public class ThreadInteractionDemo implements TestDemo{
        
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 1_000_000; i++) {
                        if (Thread.interrupted()){
                            return;
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            //当抛出异常时,会将interrupt的状态重置,重新置为false。
                            e.printStackTrace();
                            //收尾工作
                            return;
                        }
                        System.out.println("number:"+i);
                    }
                }
            };
            thread.start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
        }
    }
    

      抛出异常地方做了收尾工作后打印结果:

    interrupt2打断.gif

      结论:
        通过打印结果发现,抛出异常后,就不会执行打印,我们只需要在抛出异常地方也做收尾工作就可以了。

      Thread.sleep和SystemClock.sleep的区别
        如果只是想睡眠:我们只需要使用SystemClock.sleep(1000);
        如果想打断线程:我们可以使用Thread.sleep(1000),这个需要和thread.interrupt配合。


    1.2.wait()和notify()/notifyAll()

    在我们日常使用中可能很少使用到。调用wait,他会先释放调自己的moniter(监视器也可以认为是锁 synchronized),之后进入等待区。等待其他操作完成后需要唤醒它

    例如:

    public class WaitDemo implements TestDemo {
        private String sharedString;
    
        //初始化
        private synchronized void initString(){
           sharedString = "changhui";
        }
    
        //打印字符串
        private synchronized void printString(){
            System.out.println("String :"+sharedString);
        }
    
        /**
         * 开启两个线程,一个睡500毫秒,一个睡1000毫秒
         */
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        printString();    //先去打印
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        initString();    //去初始化
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread1.start();
        }
    }
    

    打印结果:

    wait_one.gif
    通过打印结果可以看到是null,因为我们还没进行初始化。很正常

      问题:
        那我们在开发中不知道那个先执行,哪个后执行,我们怎么办呢?
      解答:
        1.我们可以把打印的线程睡眠时间改长一点。(这个是理想状态)
        2.我们可以在打印的方法中进行判断,不等于空的时候在打印。(确定好使吗?下面我们来试试)

    //注意我们方法都是加了synchronized关键字的
    public class WaitDemo implements TestDemo {
        private String sharedString;
    
        //初始化
        private synchronized void initString(){
           sharedString = "changhui";
        }
    
        //打印字符串
        private synchronized void printString(){
            //进行判断
            while (sharedString == null){
            }
            System.out.println("String :"+sharedString);
        }
    
        /**
         * 开启两个线程,一个睡500毫秒,一个睡1000毫秒
         */
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        printString();    //先去打印
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        initString();    //去初始化
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread1.start();
        }
    }
    

    打印结果:

    wait_two.gif
    通过打印结果可以看到什么都没有打印,线程一直被执行的状态,不会被结束。

      问题1:
        为什么会一直被阻塞呢?
      解答1:
        由代码可知,我们看当执行打印方法的时候,因为有synchronized关键字,会拿到一个moniter(监视器),里边是一个死循环,始终不会结束,此时我们调用初始化操作的时候,方法也有synchronized关键字,此时因为打印方法没有释放锁,初始化操作只能进行等待,这样其实就是一个死锁。
      问题2:
        那我们如何解决呢,我们可以使用 wait() 进行暂时释放。(确定好使吗?下面我们来试试)

    //注意我们方法都是加了synchronized关键字的
    public class WaitDemo implements TestDemo {
        private String sharedString;
    
        //初始化
        private synchronized void initString(){
           sharedString = "changhui";
        }
    
        //打印字符串
        private synchronized void printString(){
            //进行判断
           while (sharedString == null){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("String :"+sharedString);
        }
    
        /**
         * 开启两个线程,一个睡500毫秒,一个睡1000毫秒
         */
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        printString();    //先去打印
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        initString();    //去初始化
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread1.start();
        }
    }
    

    打印结果:

    wait_two.gif
    通过打印结果可以看到什么都没有打印,线程还是一直被执行的状态,不会被结束。

      问题3:
        为什么呢,不是加了wait就把锁释放了吗?
      解答3:我们看下下面这幅图,就能明白了

    image.png

    通过代码测试:

    public class WaitDemo implements TestDemo {
        private String sharedString;
    
        //初始化
        private synchronized void initString(){
           sharedString = "changhui";
           notifyAll();
        }
    
        //打印字符串
        private synchronized void printString(){
            while (sharedString == null){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("String :"+sharedString);
        }
    
        /**
         * 开启两个线程,同时睡眠一个500,一个1000
         */
        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        printString();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        initString();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread1.start();
        }
    }
    

    打印结果:

    wait_four.gif
    通过打印结果可以看到打印了changhui,线程也就都结束了。
    注意:
      wait() 和 notifyAll()或者notify()也是配合使用的。
      wait() 和 notifyAll()首先需要是一个共享资源,所以都需要被synchronized关键字给包起来。如果没有使用是没有意义的,因为你等待的就是别人通知你,你要的资源已经准备就绪了。或者说我们共享的资源我已经修改过了,你可以看一看是不是你想要的样子。

    1.3.thead.join()和thread.yeild()

     thead.join()原理:
        相当于让调用join()的线程插入到当前执行该方法的线程前边,当插入的线程完全执行完成后我才继续执行。相当于吧自己wait,通过插入的线程发送通知后,我在执行。

        @Override
        public void runTest() {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        printString();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
    
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    try {
                      //相当于让调用join的线程插入到当前执行的线程前边,当插入的线程完全执行完成后我才继续执行。相当于吧自己wait,通过插入的线程通知。
                        thread.join();  
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(1000);
                        initString();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread1.start();
        }
    

     Thead.yield()原理:
         相当于我暂时让出我的时间片,把执行暂时分配给同优先级的其他线程,相当于自己暂时卡住了。等优先级高的线程执行完成后我在执行。


    2.Handler消息机制

    这篇文章我之前有说过,你们可以看一下。Handler消息机制的原理及源码分析
    说一说内存泄漏
      1.都说Handler,AsyncTask会导致内存泄漏。如何导致呢。
      内存泄漏:如何看内存泄漏呢,通过看GCRoot(garbage collector Root)引用数,没有被 GC Root 直接或间接持有引⽤的对象,会被回收。
      GCRoot分类;
        1.运行中的Thread,AsyncTask内存泄漏,不是因为他是内部类,就是因为他是一个运行中的线程GCRoot,导致的内存泄漏。(AsyncThask为Activity的内部类时,可能就是AsyncThask运行的过程中,Activity销毁了,导致的内存泄漏。)
        2.static静态对象,所有的static不会被回收,他们所引用的也不会被回收

    3.AsyncTask,HandlerThread,IntentService,Service,Executors如何选择

    1.Executors:能用就用,任务放在后台不考虑拉回来一般就用他就行
    2.如果是后台任务推到前台,考虑用AsyncTask或者Handler
    3.HandlerThread:就是一个不断在后台执行的单线程。直接使用Executors就行

    Executors和Handler区别
      Executors已经添加进来的任务,就算没执行也取消不掉。
      Handler已经添加进来的任务,就算没执行是可以取消掉的

    Service和IntentService不是线程
      Service他是后台任务的活动空间,比如你有一个音乐播放器。
      IntentService他是执行一遍任务就挂调的Service,他只是额外的有上下文,如果需要上下文的时候,我们可以使用,比如设置一个闹钟等,否则也没必要使用。

    相关文章

      网友评论

          本文标题:Java线程间通信的原理和机制

          本文链接:https://www.haomeiwen.com/subject/xequbktx.html