首先,要線程間通信的模型有兩種:共享內存和消息傳遞
題目:有兩個線程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