[網路開發]同步與執行緒安全方案
方案:synchronized,volatile+CAS(compareAndSet),atomic包,Lock介面
java同步和IO同步的對比:
都是同步非同步的方案問題,一個操作的是java程式碼(實際是堆和方法區),一個操作的是TCP RecvBuffer而已
執行緒安全方案:
https://www.cnblogs.com/jianmianruxin/p/7583262.html
---------------------
原文:https://blog.csdn.net/u010287873/article/details/82387494
http://blog.csdn.net/yangcheng33/article/details/47708631
執行緒安全實現方案
執行緒不安全的原因是:多個執行緒使用執行緒共享資料時不能保證更新操作的原子性。
執行緒安全策略:
一:互斥同步
這是一種悲觀同步方案,互斥同步要求使用共享資源的執行緒必須滿足對共享資料的訪問具備原子性。如果該執行緒沒有完全結束對共享資料的訪問,其他執行緒不得訪問共享資料。
互斥同步的實現方式有synchronized和ReentrantLock兩種方式:
前者獲取鎖的過程中獲取不到時執行緒會被放入佇列陷入阻塞等待。後者可以提供非阻塞獲取鎖的方式,比如trylock和interruptibly。
synchronized既可以修飾例項方法也可以修飾類方法,也可以作為同步塊。
1.某個執行緒獲取到物件鎖時,這個物件的所有被synchronized修飾的同步方法將被此執行緒鎖住,其他執行緒只能訪問此物件的非synchronized方法。
2.每個物件都有鎖,一個物件被一個執行緒上鎖,不影響同屬一個類的其他例項物件的鎖狀態,其他執行緒可以訪問另一個例項物件的非同步方法。
3.synchronized修飾類方法時:同步類方法被執行時所有物件的鎖狀態變為1,所有物件被上鎖。
ReentrantLock跟synchronized相比還包括了中斷鎖等候和定時鎖等候,當執行緒A先獲得了物件鎖,執行緒B在指定時間內無法獲取鎖時可以自動放棄等待該鎖。
ReentrantLock使用程式碼實現,系統無法自動釋放鎖,需要在程式碼中finally子句中顯式釋放鎖lock.unlock();Lock lock = new ReentrantLock();
lock.lock();
try{ //可能會出現執行緒安全的操作 }finally{ //一定在finally中釋放鎖 //也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候丟擲異常 lock.ublock(); }
ReentranLock的API:
ReentrantLock是Lock介面一種常見的實現,它是支援重進入的鎖即表示該鎖能夠支援一個執行緒對資源的重複加鎖。該鎖還支援獲取鎖時的公平與非公平的選擇。
- void lock() 獲取鎖,呼叫該方法當前執行緒將會獲取鎖,當鎖獲取後,該方法將返回。獲取不到會一直獲取,因此此方法是阻塞的。
- boolean tryLock() 嘗試非阻塞的獲取鎖,呼叫該方法立即返回,true表示獲取到鎖
- boolean tryLock(long time,TimeUnit unit) throws InterruptedException 和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false
- void lockInterruptibly() throws InterruptedException 可中斷獲取鎖,與lock()方法不同之處在於該方法會響應中斷,即在鎖的獲取過程中可以中斷當前執行緒
- 當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。interrupt()方法不能中斷正在執行過程中的執行緒,執行緒處於阻塞狀態才可被中斷(如執行緒呼叫了sleep,join,wait方法等),但執行緒獲取鎖的過程中不可被中斷(除了上面新學的方法lockInterruptibly)。執行緒中斷只能。
Thread.interrupt()方法不會中斷一個正在執行的執行緒。它的作用是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。更確切的說,如果執行緒被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
interrupt方法並不是強制終止執行緒,它只能設定執行緒的interrupted狀態,被block的執行緒(sleep() or join())在被呼叫interrupt時會產生InterruptException(lock是不會的,直到獲取到鎖才會去處理中斷標誌),此時是否終止執行緒由本執行緒自己決定
- 說白了執行緒的中斷方法的作用並不是中斷執行緒,而是把已經阻塞的執行緒的中斷標誌改為true(起作用的基本條件是阻塞),但這個標誌不一定立即起作用,lockInterruptibly會立即處理該標誌,但lock()方法直到獲取到了鎖才會處理中斷標誌。
- void unlock() 釋放鎖
關於鎖的重進入,其實synchronized關鍵字也支援。如前所述,synchronized關鍵字也是隱式的支援重進入而對於ReentrantLock而言,對於已經獲取到鎖的執行緒,再次呼叫lock()方法時依然可以獲取鎖而不被阻塞。
synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機制:基於執行緒的分配,而不是基於方法呼叫的分配。舉個簡單的例子,當一個執行緒執行到某個synchronized方法時,比如說method1,而在method1中會呼叫另外一個synchronized方法method2,此時執行緒不必重新去申請鎖,而是可以直接執行方法method2。
看下面這段程式碼就明白了:
1 2 3 4 5 6 7 8 9 |
class
MyClass {
public
synchronized
void
method1() {
method2();
}
public
synchronized
void
method2() {
}
}
|
上述程式碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,執行緒A執行到了method1,此時執行緒A獲取了這個物件的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重入性,此時執行緒A需要重新申請鎖。但是這就會造成一個問題,因為執行緒A已經持有了該物件的鎖,而又在申請獲取該物件的鎖,這樣就會執行緒A一直等待永遠不會獲取到的鎖。
而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。
剛剛提到的公平獲取鎖與非公平獲取鎖。如果在絕對時間上,先對於鎖進行獲取的請求一定先被滿足,那麼這個鎖就是公平的,反之就是非公平的。公平的獲取鎖也就是等待時間最久的執行緒優先獲取到鎖。ReentrantLock的建構函式來控制是否為公平鎖。
我在第一次瞭解到公平獲取鎖與非公平獲取鎖的時候,第一反應是公平獲取鎖的效率高,應該使用公平獲取鎖。但實際的情況是,非公平獲取鎖的效率遠遠大於公平獲取鎖。
java的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一個執行緒都需要作業系統來幫忙完成,因此需要從使用者態切換到核心態。對於程式碼簡單的同步塊,狀態轉換消耗的時間可能比程式碼執行的時間還要長。因此重量級的synchronized除非有必要否則不使用。API中ReentrantLock和synchronized一樣是互斥的方案,一樣是可重入的,但多了一些高階功能如可中斷,公平鎖及鎖可以繫結條件等。
二:非阻塞同步
三:可重入程式碼
如果一段程式碼的執行結果是可預測的,並且輸入相同的資料會得到相同的資料,那麼這段程式碼就是可重入程式碼,當然也是執行緒安全的。
使用者態與核心態的切換
核心態: CPU可以訪問記憶體所有資料, 包括外圍裝置, 例如硬碟, 網絡卡. CPU也可以將自己從一個程式切換到另一個程式
使用者態: 只能受限的訪問記憶體, 且不允許訪問外圍裝置. 佔用CPU的能力被剝奪, CPU資源可以被其他程式獲取
為什麼要有使用者態和核心態
由於需要限制不同的程式之間的訪問能力, 防止他們獲取別的程式的記憶體資料, 或者獲取外圍裝置的資料, 併發送到網路, CPU劃分出兩個許可權等級 -- 使用者態 和 核心態
所有使用者程式都是執行在使用者態的, 但是有時候程式確實需要做一些核心態的事情, 例如從硬碟讀取資料, 或者從鍵盤獲取輸入等. 而唯一可以做這些事情的就是作業系統, 所以此時程式就需要先作業系統請求以程式的名義來執行這些操作.
這時需要一個這樣的機制: 使用者態程式切換到核心態, 但是不能控制在核心態中執行的指令
這種機制叫系統呼叫, 在CPU中的實現稱之為陷阱指令(Trap Instruction)
他們的工作流程如下:
- 使用者態程式將一些資料值放在暫存器中, 或者使用引數建立一個堆疊(stack frame), 以此表明需要作業系統提供的服務.
- 使用者態程式執行陷阱指令
- CPU切換到核心態, 並跳到位於記憶體指定位置的指令, 這些指令是作業系統的一部分, 他們具有記憶體保護, 不可被使用者態程式訪問
- 這些指令稱之為陷阱(trap)或者系統呼叫處理器(system call handler). 他們會讀取程式放入記憶體的資料引數, 並執行程式請求的服務
- 系統呼叫完成後, 作業系統會重置CPU為使用者態並返回系統呼叫的結果
http://blog.csdn.net/yangcheng33/article/details/47708631
執行緒安全實現方案
執行緒不安全的原因是:多個執行緒使用執行緒共享資料時不能保證更新操作的原子性。
執行緒安全策略:
一:互斥同步
這是一種悲觀同步方案,互斥同步要求使用共享資源的執行緒必須滿足對共享資料的訪問具備原子性。如果該執行緒沒有完全結束對共享資料的訪問,其他執行緒不得訪問共享資料。
互斥同步的實現方式有synchronized和ReentrantLock兩種方式:
前者獲取鎖的過程中獲取不到時執行緒會被放入佇列陷入阻塞等待。後者可以提供非阻塞獲取鎖的方式,比如trylock和interruptibly。
synchronized既可以修飾例項方法也可以修飾類方法,也可以作為同步塊。
1.某個執行緒獲取到物件鎖時,這個物件的所有被synchronized修飾的同步方法將被此執行緒鎖住,其他執行緒只能訪問此物件的非synchronized方法。
2.每個物件都有鎖,一個物件被一個執行緒上鎖,不影響同屬一個類的其他例項物件的鎖狀態,其他執行緒可以訪問另一個例項物件的非同步方法。
3.synchronized修飾類方法時:同步類方法被執行時所有物件的鎖狀態變為1,所有物件被上鎖。
ReentrantLock跟synchronized相比還包括了中斷鎖等候和定時鎖等候,當執行緒A先獲得了物件鎖,執行緒B在指定時間內無法獲取鎖時可以自動放棄等待該鎖。
ReentrantLock使用程式碼實現,系統無法自動釋放鎖,需要在程式碼中finally子句中顯式釋放鎖lock.unlock();Lock lock = new ReentrantLock();
lock.lock();
try{ //可能會出現執行緒安全的操作 }finally{ //一定在finally中釋放鎖 //也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候丟擲異常 lock.ublock(); }
ReentranLock的API:
ReentrantLock是Lock介面一種常見的實現,它是支援重進入的鎖即表示該鎖能夠支援一個執行緒對資源的重複加鎖。該鎖還支援獲取鎖時的公平與非公平的選擇。
- void lock() 獲取鎖,呼叫該方法當前執行緒將會獲取鎖,當鎖獲取後,該方法將返回。獲取不到會一直獲取,因此此方法是阻塞的。
- boolean tryLock() 嘗試非阻塞的獲取鎖,呼叫該方法立即返回,true表示獲取到鎖
- boolean tryLock(long time,TimeUnit unit) throws InterruptedException 和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false
- void lockInterruptibly() throws InterruptedException 可中斷獲取鎖,與lock()方法不同之處在於該方法會響應中斷,即在鎖的獲取過程中可以中斷當前執行緒
- 當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。interrupt()方法不能中斷正在執行過程中的執行緒,執行緒處於阻塞狀態才可被中斷(如執行緒呼叫了sleep,join,wait方法等),但執行緒獲取鎖的過程中不可被中斷(除了上面新學的方法lockInterruptibly)。執行緒中斷只能。
Thread.interrupt()方法不會中斷一個正在執行的執行緒。它的作用是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。更確切的說,如果執行緒被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
interrupt方法並不是強制終止執行緒,它只能設定執行緒的interrupted狀態,被block的執行緒(sleep() or join())在被呼叫interrupt時會產生InterruptException(lock是不會的,直到獲取到鎖才會去處理中斷標誌),此時是否終止執行緒由本執行緒自己決定
- 說白了執行緒的中斷方法的作用並不是中斷執行緒,而是把已經阻塞的執行緒的中斷標誌改為true(起作用的基本條件是阻塞),但這個標誌不一定立即起作用,lockInterruptibly會立即處理該標誌,但lock()方法直到獲取到了鎖才會處理中斷標誌。
- void unlock() 釋放鎖
關於鎖的重進入,其實synchronized關鍵字也支援。如前所述,synchronized關鍵字也是隱式的支援重進入而對於ReentrantLock而言,對於已經獲取到鎖的執行緒,再次呼叫lock()方法時依然可以獲取鎖而不被阻塞。
synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機制:基於執行緒的分配,而不是基於方法呼叫的分配。舉個簡單的例子,當一個執行緒執行到某個synchronized方法時,比如說method1,而在method1中會呼叫另外一個synchronized方法method2,此時執行緒不必重新去申請鎖,而是可以直接執行方法method2。
看下面這段程式碼就明白了:
1 2 3 4 5 6 7 8 9 |
class
MyClass {
public
synchronized
void
method1() {
method2();
}
public
synchronized
void
method2() {
}
}
|
上述程式碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,執行緒A執行到了method1,此時執行緒A獲取了這個物件的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重入性,此時執行緒A需要重新申請鎖。但是這就會造成一個問題,因為執行緒A已經持有了該物件的鎖,而又在申請獲取該物件的鎖,這樣就會執行緒A一直等待永遠不會獲取到的鎖。
而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。
剛剛提到的公平獲取鎖與非公平獲取鎖。如果在絕對時間上,先對於鎖進行獲取的請求一定先被滿足,那麼這個鎖就是公平的,反之就是非公平的。公平的獲取鎖也就是等待時間最久的執行緒優先獲取到鎖。ReentrantLock的建構函式來控制是否為公平鎖。
我在第一次瞭解到公平獲取鎖與非公平獲取鎖的時候,第一反應是公平獲取鎖的效率高,應該使用公平獲取鎖。但實際的情況是,非公平獲取鎖的效率遠遠大於公平獲取鎖。
java的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一個執行緒都需要作業系統來幫忙完成,因此需要從使用者態切換到核心態。對於程式碼簡單的同步塊,狀態轉換消耗的時間可能比程式碼執行的時間還要長。因此重量級的synchronized除非有必要否則不使用。API中ReentrantLock和synchronized一樣是互斥的方案,一樣是可重入的,但多了一些高階功能如可中斷,公平鎖及鎖可以繫結條件等。
二:非阻塞同步
三:可重入程式碼
如果一段程式碼的執行結果是可預測的,並且輸入相同的資料會得到相同的資料,那麼這段程式碼就是可重入程式碼,當然也是執行緒安全的。
使用者態與核心態的切換
核心態: CPU可以訪問記憶體所有資料, 包括外圍裝置, 例如硬碟, 網絡卡. CPU也可以將自己從一個程式切換到另一個程式
使用者態: 只能受限的訪問記憶體, 且不允許訪問外圍裝置. 佔用CPU的能力被剝奪, CPU資源可以被其他程式獲取
為什麼要有使用者態和核心態
由於需要限制不同的程式之間的訪問能力, 防止他們獲取別的程式的記憶體資料, 或者獲取外圍裝置的資料, 併發送到網路, CPU劃分出兩個許可權等級 -- 使用者態 和 核心態
所有使用者程式都是執行在使用者態的, 但是有時候程式確實需要做一些核心態的事情, 例如從硬碟讀取資料, 或者從鍵盤獲取輸入等. 而唯一可以做這些事情的就是作業系統, 所以此時程式就需要先作業系統請求以程式的名義來執行這些操作.
這時需要一個這樣的機制: 使用者態程式切換到核心態, 但是不能控制在核心態中執行的指令
這種機制叫系統呼叫, 在CPU中的實現稱之為陷阱指令(Trap Instruction)
他們的工作流程如下:
- 使用者態程式將一些資料值放在暫存器中, 或者使用引數建立一個堆疊(stack frame), 以此表明需要作業系統提供的服務.
- 使用者態程式執行陷阱指令
- CPU切換到核心態, 並跳到位於記憶體指定位置的指令, 這些指令是作業系統的一部分, 他們具有記憶體保護, 不可被使用者態程式訪問
- 這些指令稱之為陷阱(trap)或者系統呼叫處理器(system call handler). 他們會讀取程式放入記憶體的資料引數, 並執行程式請求的服務
- 系統呼叫完成後, 作業系統會重置CPU為使用者態並返回系統呼叫的結果