多線程之間通信方式的總結


首先,要線程間通信的模型有兩種:共享內存和消息傳遞

題目:有兩個線程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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM