多线程之间通信方式的总结
首先,要线程间通信的模型有两种:共享内存和消息传递
题目:有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,
然后B线程执行相关的业务操作。
方式一:使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式。代码如下所示:
1 package com.springboot.study.tests.threads; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @Author: guodong 8 * @Date: 2021/1/27 15:15 9 * @Version: 1.0 10 * @Description: 11 */ 12 public class TestSync1 { 13 14 // 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知 15 static volatile boolean notice = false; 16 17 public static void main(String[] args) { 18 List<String> list = new ArrayList<>(); 19 20 // 实现线程A 21 Thread threadA = new Thread(() -> { 22 for (int i = 1; i <= 10; i++) { 23 list.add("abc"); 24 System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size()); 25 try { 26 Thread.sleep(500); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 if (list.size() == 5) 31 notice = true; 32 } 33 }); 34 35 // 实现线程B 36 Thread threadB = new Thread(() -> { 37 while (true) { 38 if (notice) { 39 System.out.println("线程B收到通知,开始执行自己的业务..."); 40 break; 41 } 42 } 43 }); 44 45 // 需要先启动线程B B相当于是监听的动作 46 threadB.start(); 47 try { 48 Thread.sleep(1000); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 53 // 再启动线程A 54 threadA.start(); 55 } 56 57 }
运行结果如下图所示:
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程A向list中添加一个元素,此时list中的元素个数为:6 线程B收到通知,开始执行自己的业务... 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10
方式二:使用Object类的wait() 和 notify() 方法
众所周知,Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁。
1 package com.springboot.study.tests.threads; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @Author: guodong 8 * @Date: 2021/1/27 15:23 9 * @Version: 1.0 10 * @Description: 11 */ 12 public class TestSync2 { 13 14 public static void main(String[] args) { 15 // 定义一个锁对象 16 Object lock = new Object(); 17 List<String> list = new ArrayList<>(); 18 19 // 实现线程A 20 Thread threadA = new Thread(() -> { 21 synchronized (lock) { 22 for (int i = 1; i <= 10; i++) { 23 list.add("abc"); 24 System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size()); 25 try { 26 Thread.sleep(500); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 if (list.size() == 5) 31 lock.notify();// 唤醒B线程 32 } 33 } 34 }); 35 36 // 实现线程B 37 Thread threadB = new Thread(() -> { 38 while (true) { 39 synchronized (lock) { 40 if (list.size() != 5) { 41 try { 42 lock.wait(); 43 } catch (InterruptedException e) { 44 e.printStackTrace(); 45 } 46 } 47 System.out.println("线程B收到通知,开始执行自己的业务..."); 48 } 49 } 50 }); 51 52 // 需要先启动线程B 53 threadB.start(); 54 try { 55 Thread.sleep(1000); 56 } catch (InterruptedException e) { 57 e.printStackTrace(); 58 } 59 60 // 再启动线程A 61 threadA.start(); 62 } 63 64 }
运行结果为:
1 线程A向list中添加一个元素,此时list中的元素个数为:1 2 线程A向list中添加一个元素,此时list中的元素个数为:2 3 线程A向list中添加一个元素,此时list中的元素个数为:3 4 线程A向list中添加一个元素,此时list中的元素个数为:4 5 线程A向list中添加一个元素,此时list中的元素个数为:5 6 线程A向list中添加一个元素,此时list中的元素个数为:6 7 线程A向list中添加一个元素,此时list中的元素个数为:7 8 线程A向list中添加一个元素,此时list中的元素个数为:8 9 线程A向list中添加一个元素,此时list中的元素个数为:9 10 线程A向list中添加一个元素,此时list中的元素个数为:10 11 线程B收到通知,开始执行自己的业务...
由打印结果截图可知,在线程A发出notify()唤醒通知之后,依然是走完了自己线程的业务之后,线程B才开始执行,这也正好说明了,notify()方法不释放锁,而wait()方法释放锁。
方式三:使用JUC工具类 CountDownLatch
jdk1.5之后在java.util.concurrent包下提供了很多并发编程相关的工具类,简化了我们的并发编程代码的书写,***CountDownLatch***基于AQS框架,相当于也是维护了一个线程间共享变量state。代码如下所示:
package com.springboot.study.tests.threads; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; /** * @Author: guodong * @Date: 2021/1/27 15:34 * @Version: 1.0 * @Description: */ public class TestSync3 { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(1); List<String> list = new ArrayList<>(); // 实现线程A Thread threadA = new Thread(() -> { for (int i = 1; i <= 10; i++) { list.add("abc"); System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (list.size() == 5) countDownLatch.countDown(); } }); // 实现线程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 线程A向list中添加一个元素,此时list中的元素个数为:6 线程B收到通知,开始执行自己的业务... 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10
方式四:使用ReentrantLock结合Condition
1 package com.springboot.study.tests.threads; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.locks.Condition; 6 import java.util.concurrent.locks.ReentrantLock; 7 8 /** 9 * @Author: guodong 10 * @Date: 2021/1/27 20:09 11 * @Version: 1.0 12 * @Description: 13 */ 14 public class TestSync4 { 15 16 public static void main(String[] args) { 17 ReentrantLock lock = new ReentrantLock(); 18 Condition condition = lock.newCondition(); 19 20 List<String> list = new ArrayList<>(); 21 // 实现线程A 22 Thread threadA = new Thread(() -> { 23 lock.lock(); 24 for (int i = 1; i <= 10; i++) { 25 list.add("abc"); 26 System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size()); 27 try { 28 Thread.sleep(500); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 if (list.size() == 5) 33 condition.signal(); 34 35 } 36 lock.unlock(); 37 }); 38 39 // 实现线程B 40 Thread threadB = new Thread(() -> { 41 lock.lock(); 42 if (list.size() != 5) { 43 try { 44 condition.await(); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 } 49 System.out.println("线程B收到通知,开始执行自己的业务..."); 50 lock.unlock(); 51 }); 52 53 threadB.start(); 54 try { 55 Thread.sleep(1000); 56 } catch (InterruptedException e) { 57 e.printStackTrace(); 58 } 59 60 threadA.start(); 61 } 62 63 }
运行结果为:
1 线程A向list中添加一个元素,此时list中的元素个数为:1 2 线程A向list中添加一个元素,此时list中的元素个数为:2 3 线程A向list中添加一个元素,此时list中的元素个数为:3 4 线程A向list中添加一个元素,此时list中的元素个数为:4 5 线程A向list中添加一个元素,此时list中的元素个数为:5 6 线程A向list中添加一个元素,此时list中的元素个数为:6 7 线程A向list中添加一个元素,此时list中的元素个数为:7 8 线程A向list中添加一个元素,此时list中的元素个数为:8 9 线程A向list中添加一个元素,此时list中的元素个数为:9 10 线程A向list中添加一个元素,此时list中的元素个数为:10 11 线程B收到通知,开始执行自己的业务...
显然这种方式使用起来并不是很好,代码编写复杂,而且线程B在被A唤醒之后由于没有获取锁还是不能立即执行,也就是说,A在唤醒操作之后,并不释放锁。这种方法跟 Object 的 wait() 和 notify() 一样。
方式五:基本LockSupport实现线程间的阻塞和唤醒
LockSupport 是一种非常灵活的实现线程间阻塞和唤醒的工具,使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。
1 package com.springboot.study.tests.threads; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.locks.LockSupport; 6 7 /** 8 * @Author: guodong 9 * @Date: 2021/1/27 20:15 10 * @Version: 1.0 11 * @Description: 12 */ 13 public class TestSync5 { 14 15 public static void main(String[] args) { 16 List<String> list = new ArrayList<>(); 17 // 实现线程B 18 final Thread threadB = new Thread(() -> { 19 if (list.size() != 5) { 20 LockSupport.park(); 21 } 22 System.out.println("线程B收到通知,开始执行自己的业务..."); 23 }); 24 25 // 实现线程A 26 Thread threadA = new Thread(() -> { 27 for (int i = 1; i <= 10; i++) { 28 list.add("abc"); 29 System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size()); 30 try { 31 Thread.sleep(500); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 if (list.size() == 5) 36 LockSupport.unpark(threadB); 37 } 38 }); 39 40 threadA.start(); 41 threadB.start(); 42 } 43 44 45 }
运行结果:
1 线程A向list中添加一个元素,此时list中的元素个数为:1 2 线程A向list中添加一个元素,此时list中的元素个数为:2 3 线程A向list中添加一个元素,此时list中的元素个数为:3 4 线程A向list中添加一个元素,此时list中的元素个数为:4 5 线程A向list中添加一个元素,此时list中的元素个数为:5 6 线程A向list中添加一个元素,此时list中的元素个数为:6 7 线程B收到通知,开始执行自己的业务... 8 线程A向list中添加一个元素,此时list中的元素个数为:7 9 线程A向list中添加一个元素,此时list中的元素个数为:8 10 线程A向list中添加一个元素,此时list中的元素个数为:9 11 线程A向list中添加一个元素,此时list中的元素个数为:10