美文网首页
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线程间通信的原理和机制

    1.线程间通信 1.1.thread.interrupt()   我们理解的可能就是线程和线程之间对话。   最简...

  • java内存模型

    并发编程模型的两个问题 线程间如何进行通信,通信是指线程间以何种机制来交换信息。线程间通信的机制有两种:共享内存和...

  • java内存模型

    java内存模型基础 并发编程,两个关键问题:线程通信和线程同步通信机制:共享内存和消息传递 java并发采用共享...

  • 线程间通信剖析——Java进阶

    Java线程间通信剖析 本文将介绍常用的线程间通信工具CountDownLatch、CyclicBarrier和P...

  • java代码中使用多线程wait/notify18

    java的wait/notify的通知机制可以用来实现线程间通信。wait表示线程的等待,调用该方法会导致线程阻塞...

  • Java wait和notify/notifyAll的使用方法

    java的wait/notify的通知机制可以用来实现线程间通信。wait表示线程的等待,调用该方法会导致线程阻塞...

  • 3. Java内存模型

    线程间通信机制有两种:共享内存、消息传递,Java并发采用的前者(堆内存和线程本地内存见得数据同步); 指令重排序...

  • java 多线程概述

    线程 线程优先级 线程的六个状态 守护线程 启动和终止线程 线程间通信 等待/通知机制

  • Java内存模型

    线程间有两种通信机制:共享内存和消息传递。 Java采用共享内存模型。 happens-before规则 如果一个...

  • 线程间通信

    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。 1)锁机制:包括互斥锁、条...

网友评论

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

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