python入門python的基本語法
540
2025-04-01
概述
案例
模擬票
測試類
線程同步
同步代碼塊
格式
同步鎖
同步方法
格式
代碼
Lock 鎖
概述
如果有過個線程在同時運行, 而這些線程可能會勇士運行這段代碼. 程序每次運行結果和單線程運行的結果是一樣的, 而其他的變量的值也和預期的是一樣的, 就是線程安全的.
案例
我們通過一個案例, 演示線程的安全問題:
電影院要賣票, 我們模擬電影院的賣過程. 假設要播放的電影是 “郭德綱和他嫂子的愛情故事”. 本次電影的座位共有 100 個. (本場電影只能賣 100 張票)
我們來模擬電影院的售票窗口, 實現多個窗口同時賣 “郭德綱和他嫂子的愛情故事” 這場電影票. (多個窗口一起賣這 100 張票)
窗口采用線程對象來模擬, 票采用 Runnable 接口子類來模擬.
模擬票
public class Ticket implements Runnable { private int ticket = 100; /** * 執行賣票操作 */ @Override public void run() { // 每個窗口賣票的操作 // 窗口永遠開啟 while (true) { if (ticket > 0) { // 有票可賣 // 出票操作 // 使用sleep模擬一下出票時間 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取當前線程對象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在賣: " + ticket--); } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
測試類
public class Test51 { public static void main(String[] args) { // 創建線程任務對象 Ticket ticket = new Ticket(); // 創建三個窗口對象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); // 同時賣票 t1.start(); t2.start(); t3.start(); } } 輸出結果: 窗口1正在賣: 99 窗口3正在賣: 98 窗口2正在賣: 100 窗口2正在賣: 97 窗口1正在賣: 96 窗口3正在賣: 95 窗口2正在賣: 94 窗口1正在賣: 93 窗口3正在賣: 92 窗口3正在賣: 91 窗口1正在賣: 90 窗口2正在賣: 89 窗口2正在賣: 88 窗口3正在賣: 86 窗口1正在賣: 87 窗口1正在賣: 85 窗口2正在賣: 84 窗口3正在賣: 83 窗口1正在賣: 82 窗口2正在賣: 81 窗口3正在賣: 80 窗口3正在賣: 79 窗口2正在賣: 78 窗口1正在賣: 77 窗口3正在賣: 76 窗口2正在賣: 75 窗口1正在賣: 74 窗口1正在賣: 73 窗口2正在賣: 72 窗口3正在賣: 71 窗口1正在賣: 70 窗口2正在賣: 69 窗口3正在賣: 68 窗口3正在賣: 67 窗口1正在賣: 65 窗口2正在賣: 66 窗口1正在賣: 64 窗口2正在賣: 62 窗口3正在賣: 63 窗口3正在賣: 61 窗口1正在賣: 60 窗口2正在賣: 59 窗口2正在賣: 58 窗口1正在賣: 57 窗口3正在賣: 56 窗口3正在賣: 55 窗口2正在賣: 53 窗口1正在賣: 54 窗口1正在賣: 52 窗口3正在賣: 51 窗口2正在賣: 50 窗口1正在賣: 49 窗口2正在賣: 48 窗口3正在賣: 47 窗口2正在賣: 46 窗口3正在賣: 45 窗口1正在賣: 44 窗口1正在賣: 43 窗口3正在賣: 42 窗口2正在賣: 41 窗口2正在賣: 40 窗口3正在賣: 39 窗口1正在賣: 38 窗口1正在賣: 37 窗口3正在賣: 36 窗口2正在賣: 35 窗口1正在賣: 34 窗口3正在賣: 33 窗口2正在賣: 32 窗口2正在賣: 31 窗口3正在賣: 30 窗口1正在賣: 29 窗口3正在賣: 28 窗口1正在賣: 27 窗口2正在賣: 26 窗口2正在賣: 25 窗口3正在賣: 24 窗口1正在賣: 23 窗口3正在賣: 22 窗口2正在賣: 21 窗口1正在賣: 22 窗口2正在賣: 20 窗口1正在賣: 19 窗口3正在賣: 18 窗口3正在賣: 17 窗口2正在賣: 16 窗口1正在賣: 15 窗口3正在賣: 14 窗口2正在賣: 13 窗口1正在賣: 12 窗口1正在賣: 11 窗口2正在賣: 10 窗口3正在賣: 9 窗口3正在賣: 8 窗口2正在賣: 7 窗口1正在賣: 6 窗口2正在賣: 5 窗口1正在賣: 5 窗口3正在賣: 4 窗口2正在賣: 3 窗口1正在賣: 2 窗口3正在賣: 1 窗口2正在賣: 0 窗口1正在賣: -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
發現程序出現了兩個問題:
相同的票數, 比如 5 這張票被賣了兩回
不存在的票, 比如 0 票與 -1, 是不存在的
這種問題, 幾個窗口 (線程)票數不同了, 這種問題成為線程不安全.
線程安全問題都是由全局變量及靜態變量引起的. 若每個線程中對全局變量, 靜態變量只有讀操作, 而無寫操作, 一般來說, 這個全局變量是線程安全的. 若有多個線程同時執行操作, 一般都需要考慮線程同步, 否則的話就可能影響線程安全.
線程同步
當我們使用多個線程訪問同一資源的時候, 且多個線程中對資源有寫的操作, 就容易出現線程安全問題.
要解決上述多線程并發訪問一個資源的安全性問題. 也就是解決重復票與不存在票問題. Java 中提供了 (synchronized) 來解決.
根據案例描述:
窗口 1 線程進入操作的時候, 窗口 2 和窗口 3
線程只能在外等著. 窗口 1 操作結束, 窗口 1 和窗口 3有機會去執行. 也就是說在某個線程修改共享資源的時候, 其他線程不能去修改該資源, 等待修改完畢同步之后,才能去搶奪 CPU 資源, 完成對應的操作, 保證了數據的同步性, 解決了線程不安全的現象.
為了保證每個線程都能正常秩序原子操作 Java 引入了線程同步機制.
那么怎么去使用呢? 有三種方式完成同步操作:
同步代碼塊
同步方法
鎖機制
同步代碼塊
同步代碼塊: synchronized 關鍵字可以用于方法中的某個區塊中. 表示只對這個區塊的資源實行互斥訪問.
格式
synchronized(同步鎖){ 需要同步操作的代碼 }
1
2
3
同步鎖
對象的同步鎖只是一個概念, 可以想象為在對象上標記了一個鎖:
鎖對象, 可也是任意類型
多個線程對象, 要使用同一把鎖
注: 在任何時候, 最多允許一個線程擁有同步鎖. 誰拿到所就進入代碼塊. 其他的線程只能在外面等著. (Blocked)
使用同步代碼塊解決代碼:
public class Ticket implements Runnable { private int ticket = 100; Object lock = new Object(); /** * 執行賣票操作 */ @Override public void run() { // 每個窗口賣票的操作 // 窗口, 永遠開啟 while (true) { synchronized (lock) { if (ticket > 0) { // 有票可賣 // 出票操作 // 使用sleep模擬一下出票時間 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取當前線程對象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在賣: " + ticket--); } } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
當使用了同步代碼塊后, 上述的線程的安全問題, 解決了.
同步方法
同步方法: 使用 synchronized 修飾的方法, 就叫做同步方法. 保證 A 線程執行該方法的時候, 其他線程只能在方法外等著.
格式
public synchronized void method(){ 可能會產生線程安全問題的代碼 }
1
2
3
同步鎖是誰?
對于非 static 方法, 同步鎖就是 this. 對于 static 方法, 我們使用當前方法所在類的字節碼對象 (類名.class)
代碼
public class Ticket implements Runnable { private int ticket = 100; /** * 執行賣票操作 */ @Override public void run() { // 每個窗口賣票的操作 // 窗口永遠開啟 while (true){ } } /** * 鎖對象是誰調用這個方法就是誰 * 隱含鎖對象就是this */ public synchronized void sellTicket(){ if(ticket > 0){ // 有票可以賣 // 出票操作 // 使用sleep模擬一下出票時間 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取當前線程對象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在賣: " + ticket--); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Lock 鎖
java.util.concurrent.locks.Lock機制提供了比 synchronized 代碼塊和 synchronized 方法更廣泛的鎖定操作, 同步代碼塊 / 同步方法具有功能 Lock 都有, 除此之外更強大, 更體現面向對象.
Lock 鎖也稱為同步鎖, 加鎖與釋放鎖方法如下:
public void lock(): 加同步鎖
public void unlock(): 釋放同步鎖
使用如下:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticket implements Runnable { private int ticket = 100; Lock lock = new ReentrantLock(); /** * 執行賣票操作 */ @Override public void run() { // 每個窗口賣票的操作 // 窗口永遠開啟 while (true){ lock.lock(); if(ticket > 0){ // 有票可以賣 // 出票操作 // 使用sleep模擬一下出票時間 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 獲取當前線程對象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在賣: " + ticket--); } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。