系統化學習多線程(二)-線程同步-等待-通知

1.大綱

-------------------------學前必讀----------------------------------java

學習不能快速成功,但必定能夠快速入門
總體課程思路:
1.實踐爲主,理論化偏少
2.課程筆記有完整的案例和代碼,(爲了學習效率)再開始以前我會簡單粗暴的介紹知識點案例思路,
有基礎的同窗聽了以後能夠直接結合筆記寫代碼,
若是沒聽懂再向下看視頻,我會手把手編寫代碼和演示測試結果;
3.重要提示,學編程和學游泳同樣,多實踐學習效率才高,理解才透徹;
4.編碼功底差的建議每一個案例代碼寫三遍,至於爲何...<<賣油翁>>...老祖宗的智慧編程

-------------------------------------------------------------------------多線程

2.線程同步

1.synchronized鎖住的是括號裏的對象,而不是代碼。
2.經常使用鎖對象(this,字節碼)
  1. 當synchronized (TicketThread.class),程序執行正常,只用一份字節碼,誰擁有字節碼誰就有執行權
  2. synchronized (lockObject),程序執行正常,由於整個應用上下文只有一個lockObject對象,誰擁有lockObject對象,誰就有執行權
  3. synchronized (this),有重複售票的現象,this表明當前對象,擁有當前對象就擁有執行權,而在時間調用中咱們建立了多個TicketThread對象,所以鎖是無效的;
  4. synchronized (num),有重複售票現象,由於num是變更的,若是有2個線程都是num=99那麼只能有其中一個線程有執行權,
       可是若是num數不同能夠同時執行,這裏爲後面講出售不一樣的班次的車票時提升鎖的效率埋下伏筆ide

3.使用同步方法是,不須要手動添加同步監聽對象,
  若是是實例方法,那麼默認的同步監聽對象就是this,
  若是是靜態方法,默認的同步監聽對象是類的字節碼對象;
4.注意,當使用字節碼鎖時,使用了synchronized (TicketThread.class) 的地方都會被鎖住,即便在不一樣的方法內;性能

5.ReentrantLock提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做,性能更高。學習

6.若是要對不一樣的數據加鎖,應該怎麼辦,好比購票業務中只對同一班次的車票加鎖.測試

需求:編寫模擬售票程序,驗證上面的結論ui

測試對象this

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo05Syn.TicketThread;
 4 import org.junit.Test;
 5 
 6 /**
 7  * @author 姿式帝-博客園
 8  * @address https://www.cnblogs.com/newAndHui/
 9  * @WeChat 851298348
10  * @create 05/05 10:53
11  * @description 線程同步
12  * 1.同步代碼塊
13  * synchronized鎖住的是括號裏的對象,而不是代碼。
14  * 2.同步方法
15  * 使用同步方法是,不須要手動添加同步監聽對象,
16  * 若是是實例方法,那麼默認的同步監聽對象就是this,
17  * 若是是靜態方法,默認的同步監聽對象是類的字節碼對象;
18  * 3.同步鎖-ReentrantLock
19  */
20 public class Test08Syn {
21     /**
22      * 測試:
23      */
24     @Test
25     public void testTicketThread() throws InterruptedException {
26         System.out.println("---test start-------");
27         TicketThread thread1 = new TicketThread();
28         thread1.setName("窗口1");
29         TicketThread thread2 = new TicketThread();
30         thread2.setName("窗口2");
31         TicketThread thread3 = new TicketThread();
32         thread3.setName("窗口3");
33         // 開啓線程
34         thread1.start();
35         thread2.start();
36         thread3.start();
37         Thread.sleep(10);
38         //測試普通方法可否被調用
39         //thread1.method1();
40         System.out.println("==========等待售票============");
41         Thread.sleep(10 * 1000);
42         System.out.println("---test end-------");
43     }
44 }

線程對象編碼

  1 package com.wfd360.thread.demo05Syn;
  2 
  3 import java.util.concurrent.locks.ReentrantLock;
  4 
  5 /**
  6  * @author 姿式帝-博客園
  7  * @address https://www.cnblogs.com/newAndHui/
  8  * @WeChat 851298348
  9  * @create 05/04 11:55
 10  * @description <p>
 11  * 模擬多線程售票
 12  * </p>
 13  */
 14 public class TicketThread extends Thread {
 15     // 假定票老是100張
 16     private static Integer num = 100;
 17     // 鎖對象
 18     private static Object lockObject = new Object();
 19     // 同步鎖
 20     private static final ReentrantLock lock = new ReentrantLock();
 21 
 22     @Override
 23     public void run() {
 24         while (num > 0) {
 25             sellTicketLock();
 26         }
 27         System.out.println("===售票結束===");
 28     }
 29 
 30     /**
 31      * 當使用字節碼鎖時,使用了synchronized (TicketThread.class) 的地方都會被鎖住,即便在不一樣的方法內
 32      */
 33     public void method1() {
 34         synchronized (TicketThread.class) {
 35             System.out.println("=======method1=============");
 36         }
 37     }
 38 
 39     /**
 40      * 同步代碼塊實現
 41      * 探討synchronized鎖的是什麼?
 42      * synchronized鎖住的是括號裏的對象,而不是代碼。
 43      * <p>
 44      * 1. 當synchronized (TicketThread.class),程序執行正常,只用一份字節碼,誰擁有字節碼誰就有執行權
 45      * 2. synchronized (lockObject),程序執行正常,由於整個應用上下文只有一個lockObject對象,誰擁有lockObject對象,誰就有執行權
 46      * 3. synchronized (this),有重複售票的現象,this表明當前對象,擁有當前對象就擁有執行權,而在時間調用中咱們建立了多個TicketThread對象,所以鎖是無效的;
 47      * 4. synchronized (num),有重複售票現象,由於num是變更的,若是有2個線程都是num=99那麼只能有其中一個線程有執行權,
 48      * 可是若是num數不同能夠同時執行,這裏爲後面講出售不一樣的班次的車票時提升鎖的效率埋下伏筆
 49      */
 50     private void sellTicket() {
 51         synchronized (TicketThread.class) {
 52             if (num > 0) {
 53                 // 獲取線程名稱
 54                 String threadName = this.getName();
 55                 System.out.println(threadName + "-正在出售第" + num + "張票");
 56                 // 模擬售票耗時20毫秒
 57                 try {
 58                     Thread.sleep(50);
 59                 } catch (InterruptedException e) {
 60                     e.printStackTrace();
 61                 }
 62                 --num;
 63             }
 64         }
 65     }
 66 
 67     /**
 68      * 同步方法實現鎖
 69      * 使用同步方法是,不須要手動添加同步監聽對象,
 70      * 若是是實例方法,那麼默認的同步監聽對象就是this,
 71      * 若是是靜態方法,默認的同步監聽對象是類的字節碼對象;
 72      * 1.實例方法 private synchronized void sellTicketSyn(),默認的同步監聽對象就是this,只能鎖住同一個對象,在當前使用會出現重複售票;
 73      * 2.靜態方法 private static synchronized void sellTicketSyn(),同步監聽對象是類的字節碼對象,至關於全局鎖;
 74      */
 75     private static void sellTicketSyn() {
 76         if (num > 0) {
 77             // 獲取線程名稱
 78             // String threadName = this.getName();
 79             String threadName = Thread.currentThread().getName();
 80             System.out.println(threadName + "-正在出售第" + num + "張票");
 81             // 模擬售票耗時20毫秒
 82             try {
 83                 Thread.sleep(50);
 84             } catch (InterruptedException e) {
 85                 e.printStackTrace();
 86             }
 87             --num;
 88         }
 89     }
 90 
 91     /**
 92      * 同步鎖-ReentrantLock
 93      * 1.加鎖 lock.lock();
 94      * 2.必須手動釋放鎖 lock.unlock();
 95      */
 96     private void sellTicketLock() {
 97         // 對代碼加鎖
 98         lock.lock();
 99         try {
100             if (num > 0) {
101                 // 獲取線程名稱
102                 // String threadName = this.getName();
103                 String threadName = Thread.currentThread().getName();
104                 System.out.println(threadName + "-同步鎖正在出售第" + num + "張票");
105                 // 模擬售票耗時20毫秒
106                 try {
107                     Thread.sleep(50);
108                 } catch (InterruptedException e) {
109                     e.printStackTrace();
110                 }
111                 --num;
112             }
113         } finally {
114             // 釋放鎖
115             lock.unlock();
116         }
117     }
118 }

3.線程等待與喚醒

需求:模擬蓄水池流量限制,爲了簡單更明白的理解線程等待與通知,這裏假定模型爲極端狀況,
假設蓄水池的正常水位是1000噸,當有水時每次取走水1000噸,當沒有水時每次加入1000噸,讓這兩個動做交互進行
分析:
1.編寫水池對象,並提供當前水位字段,取水方法,加水方法
/**
* 加水方法
* 1.synchronized 對同一個水池採用同步斷定
* 2.判斷是否有水,有水則等待(this.wait()),不然加水
* 3.加完水後進行通知(this.notify())
*/
/**
* 取水方法
* 1.synchronized 對同一個水池採用同步斷定
* 2.判斷是否有水,無水則等待(this.wait()),不然取水
* 3.取完水後進行通知(this.notify())
*/
2.編寫2個線程,一個線程取水,一個線程加水
3.啓動測試,建立一個水池對象,將對象分別傳入取水線程和加水線程,並啓動線程

實現代碼

測試對象

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo06Wait.AddPollThread;
 4 import com.wfd360.thread.demo06Wait.Pool;
 5 import com.wfd360.thread.demo06Wait.ReducePollThread;
 6 
 7 /**
 8  * @author 姿式帝-博客園
 9  * @address https://www.cnblogs.com/newAndHui/
10  * @WeChat 851298348
11  * @create 05/05 3:18
12  * @description <p>
13  * 需求:模擬蓄水池流量限制,爲了簡單更明白的理解線程等待與通知,這裏假定模型爲極端狀況,
14  * 假設蓄水池的正常水位是1000噸,當有水時每次取走水1000噸,當沒有水時每次加入1000噸,讓這兩個動做交互進行
15  * 分析:
16  * 1.編寫水池對象,並提供當前水位字段,取水方法,加水方法
17  * 2.編寫2個線程,一個線程取水,一個線程加水
18  * 3.啓動測試,建立一個水池對象,將對象分別傳入取水線程和加水線程
19  *
20  * </p>
21  */
22 public class Test09Wait {
23     /**
24      * 測試加水取水交替進行,理解等待與通知
25      *
26      * @param args
27      */
28     public static void main(String[] args) {
29         // 建立水池對象
30         Pool pool = new Pool();
31         pool.setNum(1000);
32         // 建立加水線程,並啓動
33         new AddPollThread(pool, "加水線程").start();
34         // 建立取水線程,並啓動
35         new ReducePollThread(pool, "取水線程").start();
36     }
37 }
Test09Wait

水池對象

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿式帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:28
 8  * @description <p>
 9  * 水池對象
10  * </p>
11  */
12 public class Pool {
13     // 水池編號
14     private Integer id;
15     // 水池當前水量
16     private Integer num;
17 
18     /**
19      * 加水方法
20      * 1.synchronized 對同一個水池採用同步斷定
21      * 2.判斷是否有水,有水則等待,不然加水
22      * 3.加完水後進行通知
23      */
24     public synchronized void addPoll() throws Exception {
25         String name = Thread.currentThread().getName();
26         // 當水位大於或等於1000時,線程進入等待狀態
27         if (num >= 1000) {
28             System.out.println(name + "-進入等待加水狀態,num=" + num);
29             this.wait();
30         }
31         System.out.println(name + "-準備加水,num=" + num);
32         this.num += 1000;
33         System.out.println(name + "-加水完成,num=" + num);
34         this.notify();
35     }
36     /**
37      * 取水方法
38      * 1.synchronized 對同一個水池採用同步斷定
39      * 2.判斷是否有水,無水則等待,不然取水
40      * 3.取完水後進行通知
41      */
42     public synchronized void reducePoll() throws Exception {
43         String name = Thread.currentThread().getName();
44         // 當水位大於或等於1000時,線程進入等待狀態
45         if (num < 1000) {
46             System.out.println(name + "-進入等待--取水--狀態,num=" + num);
47             this.wait();
48         }
49         System.out.println(name + "-準備--取水--,num=" + num);
50         this.num -= 1000;
51         System.out.println(name + "---取水--完成,num=" + num);
52         this.notify();
53     }
54 
55     public Integer getId() {
56         return id;
57     }
58 
59     public void setId(Integer id) {
60         this.id = id;
61     }
62 
63     public Integer getNum() {
64         return num;
65     }
66 
67     public void setNum(Integer num) {
68         this.num = num;
69     }
70 }
Pool

 取水線程

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿式帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:41
 8  * @description
 9  */
10 public class ReducePollThread extends Thread {
11     private Pool pool;
12 
13     // 建立線程必須傳入水池對象
14     public ReducePollThread(Pool pool,String name) {
15         this.pool = pool;
16         this.setName(name);
17     }
18 
19     @Override
20     public void run() {
21         // 循環取水100次
22         for (int i = 0; i < 100; i++) {
23             try {
24                 pool.reducePoll();
25             } catch (Exception e) {
26                 e.printStackTrace();
27             }
28         }
29     }
30 }
ReducePollThread

加水線程

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿式帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:41
 8  * @description
 9  */
10 public class AddPollThread extends Thread {
11     private Pool pool;
12 
13     // 建立線程必須傳入水池對象
14     public AddPollThread(Pool pool,String name) {
15         this.pool = pool;
16         // 設置線程名稱
17         this.setName(name);
18     }
19 
20     @Override
21     public void run() {
22         // 循環加水100次
23         for (int i = 0; i < 100; i++) {
24             try {
25                 pool.addPoll();
26             } catch (Exception e) {
27                 e.printStackTrace();
28             }
29         }
30     }
31 }
AddPollThread

測試結果

 完美!

系統化的在線學習:點擊進入學習

相關文章
相關標籤/搜索