zoukankan      html  css  js  c++  java
  • 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
      
  • 相关阅读:
    SQL2005 镜像配置
    子窗体关闭程序
    asp.net 输出微信自定义菜单json
    教是最好的学
    人为什么要努力?
    《雪国列车》制度与自由
    时间记录APP———Time Meter
    饭饭
    Android编译程序报错:Re-installation failed due to different application signatures.
    我的GTD起步
  • 原文地址:https://www.cnblogs.com/ronnieyuan/p/13667914.html
Copyright © 2011-2022 走看看