Java实现线程间通信方式

线程间通信的模型:

  • 共享内存

  • 消息传递

我们来做道题理解一下

题目: 有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作。

方法1: 使用volatile关键字

  • 使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
  • 是最简单的一种实现方式。
package com.ronnie.leetcode.tel;

import java.util.ArrayList;
import java.util.List;

public class TestSync01 {

    // 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知
    static volatile boolean notice = false;

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        // 实现线程A
        Thread threadA = new Thread(() ->{
           for (int i = 1; i <= 10; i++){
               list.add("nga");
               System.out.println("线程A向列表中添加一个元素, 此时list中元素个数为: " + list.size());
               try {
                   Thread.sleep(500);
               } catch (InterruptedException e){
                   e.printStackTrace();
               }
               if (list.size() == 5)
                   notice = true;
           }
        });

        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true){
                if (notice){
                    System.out.println("线程N收到通知, 开始执行自己的业务: ");
                    break;
                }
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }
}

  • 实际运行结果与我们想的有些出入

    线程A向列表中添加一个元素, 此时list中元素个数为: 1
    线程A向列表中添加一个元素, 此时list中元素个数为: 2
    线程A向列表中添加一个元素, 此时list中元素个数为: 3
    线程A向列表中添加一个元素, 此时list中元素个数为: 4
    线程A向列表中添加一个元素, 此时list中元素个数为: 5
    线程A向列表中添加一个元素, 此时list中元素个数为: 6
    线程N收到通知, 开始执行自己的业务: 
    线程A向列表中添加一个元素, 此时list中元素个数为: 7
    线程A向列表中添加一个元素, 此时list中元素个数为: 8
    线程A向列表中添加一个元素, 此时list中元素个数为: 9
    线程A向列表中添加一个元素, 此时list中元素个数为: 10
    
  • 将try catch放到if判断之后则能达到预期的输出

    线程A向list中添加一个元素,此时list中的元素个数为:1
    线程A向list中添加一个元素,此时list中的元素个数为:2
    线程A向list中添加一个元素,此时list中的元素个数为:3
    线程A向list中添加一个元素,此时list中的元素个数为:4
    线程A向list中添加一个元素,此时list中的元素个数为:5
    线程B收到通知,开始执行自己的业务...
    线程A向list中添加一个元素,此时list中的元素个数为:6
    线程A向list中添加一个元素,此时list中的元素个数为:7
    线程A向list中添加一个元素,此时list中的元素个数为:8
    线程A向list中添加一个元素,此时list中的元素个数为:9
    线程A向list中添加一个元素,此时list中的元素个数为:10
    
    • 原因是:

      • 在第5次的时候线程B从阻塞状态过渡了可以竞争锁的状态,但是它并不一定就能立刻获取CPU的执行权

方法2: 使用Object类的wait()和notify() 方法

  • 实现方式: 消息传递
  • 需要注意的是:
    • wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
package com.ronnie.leetcode.tel;

import java.util.ArrayList;

public class TestSync02 {
    public static void main(String[] args) {
        // 定义一个锁对象
        Object lock = new Object();

        ArrayList<String> list = new ArrayList<>();

        // 实现线程A
        Thread threadA = new Thread(() -> {
            synchronized (lock){
               for (int i = 1; i <= 10; i++){
                   list.add("nga");
                   System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
                   try {
                       Thread.sleep(500);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   if (list.size() == 5)
                       // 唤醒 B线程
                       lock.notify();
               }
            }
        });

        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true){
                synchronized (lock){
                    if(list.size() != 5){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 再启动线程A
        threadA.start();
    }
}

  • 执行结果

    线程A向list中添加一个元素,此时list中的元素个数为:1
    线程A向list中添加一个元素,此时list中的元素个数为:2
    线程A向list中添加一个元素,此时list中的元素个数为:3
    线程A向list中添加一个元素,此时list中的元素个数为:4
    线程A向list中添加一个元素,此时list中的元素个数为:5
    线程A向list中添加一个元素,此时list中的元素个数为:6
    线程A向list中添加一个元素,此时list中的元素个数为:7
    线程A向list中添加一个元素,此时list中的元素个数为:8
    线程A向list中添加一个元素,此时list中的元素个数为:9
    线程A向list中添加一个元素,此时list中的元素个数为:10
    线程B收到通知,开始执行自己的业务...
    
  • 线程A发出notify()唤醒通知之后,依然是走完了自己线程的业务之后,线程B才开始执行,这也正好说明了,notify()方法不释放锁,而wait()方法释放锁。

方法3: 使用JUC工具类 CountDownLatch()

package com.ronnie.leetcode.tel;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

public class TestSync03 {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ArrayList<String> list = new ArrayList<>();

        // 实现线程A
        Thread threadA = new Thread(() -> {
            for (int i = 0; i <= 10; i++){
                list.add("pcr");
                System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());

                if (list.size() == 5)
                    countDownLatch.countDown();

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true){
                if (list.size() != 5){
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程B收到通知,开始执行自己的业务...");
                break;
            }
        });

        // 先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在启动线程A
        threadA.start();
    }
}

  • 执行结果

    线程A向list中添加一个元素,此时list中的元素个数为:1
    线程A向list中添加一个元素,此时list中的元素个数为:2
    线程A向list中添加一个元素,此时list中的元素个数为:3
    线程A向list中添加一个元素,此时list中的元素个数为:4
    线程A向list中添加一个元素,此时list中的元素个数为:5
    线程B收到通知,开始执行自己的业务...
    线程A向list中添加一个元素,此时list中的元素个数为:6
    线程A向list中添加一个元素,此时list中的元素个数为:7
    线程A向list中添加一个元素,此时list中的元素个数为:8
    线程A向list中添加一个元素,此时list中的元素个数为:9
    线程A向list中添加一个元素,此时list中的元素个数为:10
    线程A向list中添加一个元素,此时list中的元素个数为:11
    

方法4: 使用ReentrantLock结合Condition

package com.ronnie.leetcode.tel;

import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestSync04 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        ArrayList<String> list = new ArrayList<>();

        // 实现线程A
        Thread threadA = new Thread(() -> {
            lock.lock();
            for (int i = 0; i <= 10; i++){
                list.add("pcr");
                System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    condition.signal();
            }
        });

        // 实现线程B
        Thread threadB = new Thread(() -> {
            lock.lock();
                if (list.size() != 5){
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            System.out.println("线程B收到通知,开始执行自己的业务...");
            lock.unlock();
        });


        // 先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在启动线程A
        threadA.start();
    }
}

  • 执行结果

    线程A向list中添加一个元素,此时list中的元素个数为:1
    线程A向list中添加一个元素,此时list中的元素个数为:2
    线程A向list中添加一个元素,此时list中的元素个数为:3
    线程A向list中添加一个元素,此时list中的元素个数为:4
    线程A向list中添加一个元素,此时list中的元素个数为:5
    线程A向list中添加一个元素,此时list中的元素个数为:6
    线程A向list中添加一个元素,此时list中的元素个数为:7
    线程A向list中添加一个元素,此时list中的元素个数为:8
    线程A向list中添加一个元素,此时list中的元素个数为:9
    线程A向list中添加一个元素,此时list中的元素个数为:10
    线程A向list中添加一个元素,此时list中的元素个数为:11
    
  • 可以看到线程B在被A唤醒之后由于没有获取锁还是不能立即执行,也就是说,A在唤醒操作之后,并不释放锁。

  • 与Object类的wait()和notify()一样。

方法5: 基本LockSupport实现线程间的阻塞和唤醒(推荐)

  • 优点:
    • 灵活
    • 不需要关注是等待线程先进行还是唤醒线程先运行
  • 需要知道线程的名字
package com.ronnie.leetcode.tel;

import java.util.ArrayList;
import java.util.concurrent.locks.LockSupport;

public class TestSync05 {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        // 实现线程B
        final Thread threadB = new Thread(() -> {
            if (list.size() != 5) {
                LockSupport.park();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        // 实现线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++){
                list.add("pcr");
                System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());

                if (list.size() == 5)
                    LockSupport.unpark(threadB);

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

  • 执行结果

    线程A向list中添加一个元素,此时list中的元素个数为:1
    线程A向list中添加一个元素,此时list中的元素个数为:2
    线程A向list中添加一个元素,此时list中的元素个数为:3
    线程A向list中添加一个元素,此时list中的元素个数为:4
    线程A向list中添加一个元素,此时list中的元素个数为:5
    线程B收到通知,开始执行自己的业务...
    线程A向list中添加一个元素,此时list中的元素个数为:6
    线程A向list中添加一个元素,此时list中的元素个数为:7
    线程A向list中添加一个元素,此时list中的元素个数为:8
    线程A向list中添加一个元素,此时list中的元素个数为:9
    线程A向list中添加一个元素,此时list中的元素个数为:10
    
posted @ 2020-09-14 17:24  wellDoneGaben  阅读(460)  评论(0编辑  收藏  举报