【并发编程】线程间通信的模型

线程之间通信的主要方式有两种:

  1. 共享内存
  2. 消息传递

举例:
有两个线程 A 和 B,A 线程向 list 集合里添加元素 abc,一共添加10次,要求当添加到第5次的时候,通知线程 B 执行相关业务。

方式1:使用 volatitle 关键字

使用共享内存的思想,大致思路是多个线程同时监听一个变量,
当这个变量发生变化时,线程能够感知并执行相应的业务。

// 定义一个共享变量来实现通信
static volatile boolean notice = false;

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

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

经测试,volatile 通知会有一些延迟,但不会很长,在 1 ms 以内

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

基于消息传递的思想
需要注意:wait 和 notify 必须配合 synchronized 使用

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 = 0; i < 10; i++) {
               list.add("abc");
               System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
               if (list.size() == 5) {
                   // 唤醒 B线程
                   lock.notify();
                   try {
                       Thread.sleep(500);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
            }
        }
    });

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

    // 再启动线程A
    threadA.start();
    
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

经测试,需要等待线程 A 运行完成后才会执行线程 B

方法3:使用 JUC 工具类 CountDownLatch

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("abc");
            System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());

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

    // 先启动线程B
    threadB.start();

    // 在启动线程A
    threadA.start();
    
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

经测试,CountDownLatch 通知会有一些延迟,但不会很长,在 1 ms 以内

方法4:使用 ReentrantLock 可重入锁,结合 Condition

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("abc");
            System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
            if (list.size() == 5) {
                condition.signal();
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

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


    // 先启动线程B
    threadB.start();

    // 在启动线程A
    threadA.start();
    
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

经测试,ReentrantLock 结合 Condition 的方式通知会有一些延迟,但不会很长,在 1 ms 以内

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

优点:灵活,不需要关注是等待线程先运行还是唤醒线程先运行
缺点:需要知道线程的名字

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 = 0; i < 10; i++){
            list.add("abc");
            System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
            if (list.size() == 5) {
                LockSupport.unpark(threadB);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    
    threadA.start();
    threadB.start();
    
     try {
         Thread.sleep(100);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
}

经测试,LockSupport 的方式通知会有一些延迟,但不会很长,在 1 ms 以内
虽然先 unpark 不会报错,但是先执行 unpark 后,就不会执行 park 的语句块了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值