即便大部分同步裝置的功能和使用方式不一樣(鎖, semaphores, 阻塞隊列等),但它們的內部實現仍是至關一致的.換句話說它們都有相同的組成部分.瞭解這些組成部分有助於咱們設計一個同步裝置.下文會一個個分析這些組成部分.html
大部分同步裝置的目的是保證臨界區代碼在多線程環境下的安全訪問.要作到這點,一個同步裝置一般須要如下幾個部分:java
並非全部的同步裝置都由這些部分組成,可能會有例外.但大部分同步裝置都是由它們中的一到多個部分組成的.算法
同步裝置中的狀態用因而否容許一個線程取得訪問權限的檢查條件.在Lock中,狀態用來記錄一個布爾值,用於表示Lock是否已經被鎖住.在BounedSemaphore中,內部狀態用來記錄一個整型計數器,用於表示已經發送的信號數量以及可以發送的最大上限.阻塞隊列中,狀態用於記錄一個保存有相關隊項的列表,用於表示隊列中進隊的隊項,以及可以容納的最大隊項數量.安全
如下給出Lock和BoundedSemaphore的狀態實現的片斷代碼.多線程
public class Lock{
//state is kept here
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
...
}
複製代碼
public class BoundedSemaphore {
//state is kept here
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signal++;
this.notify();
}
...
}
複製代碼
訪問條件用於決定一個線程在執行檢查和設置狀態的方法時,是否容許設置狀態.同步裝置中,訪問條件是狀態的典型應用.而訪問條件的典型應用是解析成true或false,以在一個while循環中防止線程意外喚醒風險的發生.post
在Lock中,訪問條件簡化爲檢查isLocked成員變量的值.在BoundedSemaphore中,一共有兩個訪問條件決定了take()和release()方法的調用結果.當一個線程調用take()方法時須要檢查signals變量是否已經達到上限.當一個線程調用release()方法時須要一樣須要檢查signals變量.this
如下給出Lock和BoundedSemaphore的訪問條件實現的片斷代碼.咱們須要注意訪問條件一直在while循環中檢查.spa
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
//access condition
while(isLocked){
wait();
}
isLocked = true;
}
...
}
複製代碼
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
//access condition
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
//access condition
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
複製代碼
一旦線程得到訪問臨界區代碼的權限,它須要更改同步裝置內部狀態來阻塞其餘線程進入臨界區.換句話說,狀態須要反映一個線程當前正在執行臨界區代碼.這將對其餘線程檢查訪問條件取得訪問權限產生影響.線程
在Lock中,狀態更改表現爲代碼isLocked = true
.在Semaphore中變現爲代碼signals--
或是signals++
.設計
如下給出Lock和BoundedSemaphore的更改狀態實現的片斷代碼.
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
//state change
isLocked = true;
}
public synchronized void unlock(){
//state change
isLocked = false;
notify();
}
}
複製代碼
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
//state change
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
//state change
this.signals--;
this.notify();
}
}
複製代碼
一旦線程更改了同步裝置的狀態,那麼它須要通知到正在等待進入臨界區的其餘線程.由於此次狀態更改可能會將其餘線程的訪問條件置換爲true.
通知策略一共有如下三種類型:
通知全部等待線程比較簡單.只要在線程調用wait()方法的對象上調用notify()便可.調用notify()並不能保證多個等待線程中哪個被通知到.所以稱爲"隨機通知".
有時候你須要指定通知一個特定的等待線程而不是隨機通知.好比你須要按照線程調用同步裝置的順序來通知線程或是按照優先級來通知線程.那麼咱們須要存儲每個線程與調用wait()方法的對象之間的關係.當須要通知特定的等待線程時,只須要調用線程調用wait()方法的對象的notify()方法便可.這樣的例子在公平與飢餓一文中有說起.
如下給出了隨機通知一個線程的片斷代碼:
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
//wait strategy - related to notification strategy
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify(); //notification strategy
}
}
複製代碼
同步裝置一般包含有兩種類型的方法,其中檢查和設置是第一種(設置是第二種).檢查和設置是指線程調用檢查方法來比較訪問條件和同步裝置內的狀態,若是條件符合預期線程則設置同步裝置內的狀態來反映當前線程已經取得訪問臨界區的權限.
一般經過置換狀態來使訪問條件解析爲false,以阻塞其餘線程取得訪問權限.但不老是如此,例如在讀寫鎖中,一個線程經過更改讀寫鎖的內部狀態來反映該線程已經取得讀取權限,但在沒有線程請求進行寫操做的狀況下,其餘線程仍然能夠取得讀取權限.
檢查和設置操做必須是原子,即不容許其餘線程在檢查和設置狀態的期間進行干擾.
下面給出了檢查和設置方法在程序中的關鍵步驟:
若有必要能夠在檢查前設置狀態
比較訪問條件和狀態
若是訪問條件不知足預期則進入阻塞狀態
若是訪問條件知足預期則設置狀態,若有必要同時通知其餘等待線程
在以前Java中的讀寫鎖文章說起的ReadWriteLock類中的lockWrite()方法就是一個檢查和設置的實例方法.線程在調用lockWrite()時,在檢查狀態前先設置(writeRequests++).而後再經過canGrantWriteAccess()方法來對比內部狀態和訪問條件.若是符合預期則在退出調用方法前設置狀態.咱們注意到這個方法並無通知其餘等待線程.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads = new HashMap();
private int writeAccesses = 0;
private int writeRequests = 0;
private Thread writingThread;
public synchronized void lockWrite() throws InterruptedException{
writeRequests++;
Thread callingThread = Thread.currentThread();
while(! canGrantWriteAccess(callingThread)){
wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
}
複製代碼
在BoundedSemaphoer對象中包含了兩個檢查和設置方法: take()和release().兩個方法都會檢查和設置內部狀態.
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
複製代碼
設置方法是同步裝置中常見的第二種類型的方法.設置方法僅僅是設置同步裝置中的內部狀態而沒有去檢查它.一個典型的設置方法實例是Lock對象中的unlock()方法.一般一個線程在釋放已持有的鎖時不須要檢查鎖是否已經釋放過了.
下面給出了設置方法在程序中的關鍵步驟:
下面給出unlock方法示例:
public class Lock{
private boolean isLocked = false;
public synchronized void unlock(){
isLocked = false;
notify();
}
}
複製代碼
該系列博文爲筆者複習基礎所著譯文或理解後的產物,複習原文來自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial