所謂 Single Threades Execution 模式,意即「以一個線程執行」。就像獨木橋同一時間內只容許一我的通行同樣,該模式用於設置限制,以確保同一時間內只能讓一個線程執行處理。java
使用程序模擬三我的頻繁地經過一個只容許一我的通過的門情形。當人們經過門的時候,統計人數便會遞增。另外程序還會記錄通行者的「姓名和出生地」編程
名字 | 說明 |
---|---|
Main | 建立門,並讓三我的不斷地經過的類 |
Gate | 表示門的類。它會在人們經過門時記錄其姓名與出生地 |
UserThread | 表示人的類。人們不斷地經過門 |
// Main.java
public class Main {
public static void main(String[] args) {
Gate gate = new Gate();
new UserThread(gate, "Bob", "Britain").start();
new UserThread(gate, "Cao", "China").start();
new UserThread(gate, "Uber", "USA").start();
}
}
複製代碼
// Gate.java
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "NoWhere";
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
private void check() {
if (this.name.charAt(0) != this.address.charAt(0)) {
System.out.println("******** BROKEN ********** : " + toString());
}
}
@Override
public String toString() {
return "No. " + this.counter + " : " + this.name + " , " + this.address;
}
}
複製代碼
// UserThread.java
public class UserThread extends Thread {
private final Gate gate;
private final String name;
private final String address;
public UserThread(Gate gate, String name, String address) {
this.gate = gate;
this.name = name;
this.address = address;
}
@Override
public void run() {
System.out.println(this.name + " BEGIN");
while (true) {
gate.pass(this.name, this.address);
}
}
}
複製代碼
當這個程序執行時,時間點不一樣,生成的結果也會不同,如下是打印出來的 log設計模式
Bob BEGIN
Cao BEGIN
******** BROKEN ********** : No. 59622 : Bob , Britain
Uber BEGIN
******** BROKEN ********** : No. 77170 : Uber , USA
******** BROKEN ********** : No. 89771 : Uber , USA
******** BROKEN ********** : No. 93128 : Cao , China
******** BROKEN ********** : No. 95654 : Uber , USA
******** BROKEN ********** : No. 98440 : Cao , China
******** BROKEN ********** : No. 102283 : Cao , China
******** BROKEN ********** : No. 104491 : Cao , China
******** BROKEN ********** : No. 106791 : Uber , USA
******** BROKEN ********** : No. 110022 : Uber , USA
******** BROKEN ********** : No. 112073 : Uber , USA
******** BROKEN ********** : No. 113973 : Uber , USA
******** BROKEN ********** : No. 77170 : Uber , USA
******** BROKEN ********** : No. 116050 : Bob , China
******** BROKEN ********** : No. 117334 : Bob , Britain
******** BROKEN ********** : No. 119992 : Bob , USA
******** BROKEN ********** : No. 124427 : Uber , USA
******** BROKEN ********** : No. 117152 : Bob , Britain
******** BROKEN ********** : No. 129298 : Bob , China
******** BROKEN ********** : No. 130552 : Cao , Britain
******** BROKEN ********** : No. 147176 : Cao , China
******** BROKEN ********** : No. 148546 : Uber , USA
複製代碼
經過 log 能夠知道運行結果與預期不一致,因此說 Gate 類是不安全的,是非線程安全類。安全
若是仔細看一下 counter 的值,最開始顯示 BROKEN 的時候,counter 的值已經變爲了 59622。也就是說,在檢察處第一個錯誤的時候 Gate 的 pass 方法已經運行了 5 萬屢次了。在這裏,由於 UserThread 類的 run 方法執行的是無限循環,因此才檢查除了錯誤。可是若是隻測試幾回,是根本找不出錯誤的。多線程
這就是多線程程序設計的難點之一。若是檢察出錯誤,那麼說明程序並不安全。可是就算沒有檢察出錯誤,也不能說程序就必定是安全的。dom
仔細看 log 會發現還有一個奇怪的現象,好比:ide
******** BROKEN ********** : No. 59622 : Bob , Britain
複製代碼
雖然此處輸出了 BROKEN 信息,可是姓名和出生地首字母是同樣的。儘管顯示了 BROKEN,可是調試信息好像並無錯。函數
致使這種現象的緣由是,在某個線程執行 check 方法時,其餘線程不斷執行 pass 方法,改謝了 name 字段和 address 字段的值。測試
這也是多線程程序設計的難點之一。若是顯示調試信息的代碼自己就是非線程安全的,那麼顯示的調試信息就極可能是錯誤的。ui
若是連操做測試和調試信息都沒法確保安全性,那就進行代碼評審吧。多我的一塊兒仔細閱讀代碼,確認是否會發生問題,這是確保程序安全性的一個有效方法。
// Gate.java
public class Gate {
...
public synchronized void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
...
}
複製代碼
以後程序就能夠正常的運行,也不在打印 BROKEN 的 log 信息了
在剛纔的示例中,Gate 類扮演 SharedResource 的角色
SharedResource 角色是可被多個線程訪問的類,包含不少方法,但這些方法主要分爲以下兩類:
而 unsafeMethod 在被多個線程同時執行時,實例狀態有可能發生分歧。這時就須要保護該方法,使其不被多個線程同時訪問。 Java 則是經過將 unsafeMethod 聲明爲 synchronized 方法來進行保護
在該模式下,知足下列條件時,死鎖就會發生
不可分割的操做一般稱爲原子操做。
上述示例中 Gate類是線程安全的 咱們將 pass 聲明爲了 synchronized 方法,這樣 pass 方法也就成爲了原子操做
Java 編程規範中定義了一些原子操做。例如 char、int 等基本類型的賦值和引用操做都是原子的。另外,對象等引用類型的賦值和引用操做也是原子的。因爲自己就是原子的,因此就算不加上 synchronized,這些操做也不會被分割。可是 long、double 的賦值和引用操做並非原子的
總結以下:
上面介紹 Single Threaded Execution 模式用於確保某個區域「只能由一個線程」執行。下面咱們將這種模式進一步擴展,以確保某個區域「最多隻能由 N 個線程」執行。這時就要用計數信號量來控制線程數量。
java.util.concurrent 包提供了表示計數信號量的 Semaphore 類
資源的許可個數將經過 Semaphore 的構造函數來指定
Semaphore 的 acquire 方法用於確保存在可用資源。當存在可用資源時,線程會當即從 acquire 方法返回,同時信號量內部的資源個數會減 1 。 如無可用資源,線程阻塞在 acquire 方法內,直至出現可用資源。
Semaphore 的 release 方法用於釋放資源。釋放資源後,信號量內部的資源個數會增長 1。另外若是 acquire 中存在等待的線程,那麼其中一個線程會被喚醒,並從 acquire 方法返回。
// BoundedResource.java
public class BoundedResource {
private final int permits;
private final Semaphore semaphore;
private final Random random = new Random(314159);
public BoundedResource(int permits) {
this.semaphore = new Semaphore(permits);
this.permits = permits;
}
public void use() throws InterruptedException {
try {
this.semaphore.acquire();
doUse();
} finally {
this.semaphore.release();
}
}
private void doUse() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " : BEGIN used = " + (this.permits - this.semaphore.availablePermits()));
Thread.sleep(this.random.nextInt(500));
System.out.println(Thread.currentThread().getName() + " : END used = " + (this.permits - this.semaphore.availablePermits()));
}
}
複製代碼
// SemaphoreThread.java
public class SemaphoreThread extends Thread{
private final Random random = new Random(26535);
private final BoundedResource resource;
public SemaphoreThread(BoundedResource resource) {
this.resource = resource;
}
@Override
public void run() {
try {
while (true) {
this.resource.use();
Thread.sleep(this.random.nextInt(2000));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
// Main.java
public class Main {
public static void main(String[] args) {
BoundedResource boundedResource = new BoundedResource(3);
new SemaphoreThread(boundedResource).start();
new SemaphoreThread(boundedResource).start();
new SemaphoreThread(boundedResource).start();
}
}
複製代碼
打印結果:
Thread-0 : BEGIN used = 2
Thread-2 : BEGIN used = 3
Thread-1 : BEGIN used = 2
Thread-2 : END used = 3
Thread-1 : END used = 2
Thread-0 : END used = 1
Thread-2 : BEGIN used = 1
Thread-2 : END used = 1
Thread-1 : BEGIN used = 1
Thread-0 : BEGIN used = 2
Thread-1 : END used = 2
Thread-0 : END used = 1
Thread-2 : BEGIN used = 1
Thread-2 : END used = 1
Thread-1 : BEGIN used = 1
Thread-0 : BEGIN used = 2
Thread-2 : BEGIN used = 3
Thread-0 : END used = 3
複製代碼