Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLock
java的基本鎖型別,都以介面形式出現,常用的有以下兩種鎖的介面:
- Lock鎖。它的實現有ReentrantLock, ReentrantReadWriteLock.ReadLock,
ReentrantReadWriteLock.WriteLock - ReadWriteLock鎖。它的實現有ReentrantReadWriteLock。
一、lock簡單使用方法
1、Lock鎖基本都是排他鎖,它和synchronized很類似,都能對一塊程式碼進行上鎖,從而使得同一時間內只有一個執行緒能訪問。那麼有什麼差別呢?
Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的程式碼將丟擲異常,鎖就有可能永遠得不到釋放!這一點區別極為重要。忘記在 finally 塊中釋放鎖,可能會在程式中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。除此之外,當許多執行緒都在爭用同一個鎖時,使用 ReentrantLock 的總體開支卻比 synchronized 少很多。
還記得《synchronized同步 》例子嗎,我們用lock改寫一下:
class Pan1 {
private Lock lock = new ReentrantLock();
/*烹飪方法,該方法輸出步驟*/
public void Cook(String[] steps) {
lock.lock();
try{
for (int i = 0; i < steps.length; i++) {
/*模擬競爭造成的執行緒等待,這樣效果明顯些*/
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(steps[i]);
}
System.out.println("");
}finally{
/*
* synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,
* 而且在程式碼執行出現異常時,JVM會自動釋放鎖定。
* 但是使用Lock則不行,lock是通過程式碼實現的。
* 要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中
*/
lock.unlock();
}
}
/*青椒炒肉製作步驟:a1.放肉,a2.放鹽,a3.放辣椒 a4 a5....*/
String[] steps_LaJiaoChaoRou={"a1.","a2.","a3.","a4.","a5.","a6.","a7.","a8.","a9.","a10.","OK:辣椒炒肉"};
/*番茄炒蛋製作步驟:b1.放蛋,b2.放鹽,b3.放番茄*/
String[] steps_FanQieChaoDan={"b1.","b2.","b3.","b4.","b5.","b6.","OK:番茄炒蛋"};
}
public class LockDemo {
public static void main(String[] args) {
final Pan pan=new Pan();
/*執行緒1:老大炒青椒炒肉。*/
new Thread(){
public void run() {
/*為了看出錯亂效果,這裡用死迴圈,一段時間後手工點選停止執行按鈕*/
while (true) {
try {
/*青椒炒肉需要5秒;*/
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
pan.Cook(pan.steps_LaJiaoChaoRou);
}
}
}.start();
/*執行緒2:老二炒番茄炒蛋。*/
new Thread(){
public void run() {
/*為了看出錯亂效果,這裡用死迴圈,一段時間後手工點選停止執行按鈕*/
while (true) {
try {
/*番茄炒蛋需要5秒;*/
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
pan.Cook(pan.steps_FanQieChaoDan);
}
}
}.start();
}
}
有人總結了lock和synchronized同步的區別:
1. lock是一個介面,而synchronized是java的一個關鍵字,synchronized是內建的語言實現;(具體實現上的區別在《Java虛擬機器》中有講解底層的CAS不同,以前有讀過現在又遺忘了。)
2. synchronized在發生異常時候會自動釋放佔有的鎖,因此不會出現死鎖;而lock發生異常時候,不會主動釋放佔有的鎖,必須手動unlock來釋放鎖,可能引起死鎖的發生。(所以最好將同步程式碼塊用try catch包起來,finally中寫入unlock,避免死鎖的發生。)
3. lock等待鎖過程中可以用interrupt來終端等待,而synchronized只能等待鎖的釋放,不能響應中斷;
4. lock可以通過trylock來知道有沒有獲取鎖,而synchronized不能;
5. Lock可以提高多個執行緒進行讀操作的效率。(可以通過readwritelock實現讀寫分離)
二、ReadWriteLock讀寫鎖
ReadWriteLock包含兩部分:讀鎖、寫鎖。上讀鎖時,程式碼塊中資料對其它執行緒來說可讀不可寫;上寫鎖時,程式碼塊中的資料對其它執行緒來說不可寫也不可讀。由於實現了讀寫分離,在讀比寫多的場景下,這無疑更高效。
API中的例子很經典:
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
/* 初始化變數,需要寫入資料 */
if (!cacheValid) {
// 在獲取寫鎖前釋放讀鎖
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
/* 重新檢查狀態,因為其它執行緒很可能在這之前獲取了寫鎖,並改寫了資料 */
if (!cacheValid) {
System.out.println(Thread.currentThread().getName()
+ ": 快取未初始化");
data = getData();
cacheValid = true;
System.out.println(Thread.currentThread().getName()
+ ": 快取初始完成,當前值:" + data);
}
/* 降級鎖:在釋放寫鎖前獲取讀鎖 */
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); /* 釋放寫鎖,依然保持讀鎖 */
}
}
/* 此時,依然存在讀鎖 */
try {
System.out.println(Thread.currentThread().getName() + ": 獲取快取值為:"+ data);
} finally {
/* 釋放讀鎖 */
rwl.readLock().unlock();
}
}
int getData(){
return new Random().nextInt(100000);
}
}
public class LockDemo {
public static void main(String[] args) {
final CachedData cachedData = new CachedData();
for (int i = 0; i < 5; i++) {
new Thread() {
public void run() {
cachedData.processCachedData();
}
}.start();
}
}
}
輸出:
Thread-0: 快取未初始化
Thread-0: 快取未初始完成,當前值:27269
Thread-0: 獲取快取值為:27269
Thread-1: 獲取快取值為:27269
Thread-3: 獲取快取值為:27269
Thread-2: 獲取快取值為:27269
Thread-4: 獲取快取值為:27269
我們把鎖去掉,看看會輸出什麼
改寫CachedData類:
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
/* 初始化變數,需要寫入資料 */
if (!cacheValid) {
System.out.println(Thread.currentThread().getName()
+ ": 快取未初始化");
data = getData();
cacheValid = true;
System.out.println(Thread.currentThread().getName()
+ ": 快取初始完成,當前值:" + data);
}
System.out.println(Thread.currentThread().getName() + ": 獲取快取值為:"
+ data);
}
int getData(){
return new Random().nextInt(100000);
}
}
輸出:
Thread-0: 快取未初始化
Thread-1: 快取未初始化
Thread-2: 快取未初始化
Thread-0: 快取初始完成,當前值:41131
Thread-0: 獲取快取值為:41131
Thread-2: 快取初始完成,當前值:41131
Thread-2: 獲取快取值為:41131
Thread-1: 快取初始完成,當前值:11655
Thread-1: 獲取快取值為:11655
Thread-3: 獲取快取值為:11655
Thread-4: 獲取快取值為:11655
亂套了,一開始3個執行緒同時都去寫快取了,然後獲取快取也各不相同。
總結:
在運用讀寫鎖時,注意鎖的降級:
寫鎖是可以獲得讀鎖的,即:
rwl.writeLock().lock();
//在寫鎖狀態中,可以獲取讀鎖
rwl.readLock().lock();
rwl.writeLock().unlock();
讀鎖是不能夠獲得寫鎖的,如果要加寫鎖,本執行緒必須釋放所持有的讀鎖,即:
rwl.readLock().lock();
//......
//必須釋放掉讀鎖,才能夠加寫鎖
rwl.readLock().unlock();
rwl.writeLock().lock();
相關文章
- 多執行緒與併發-----Lock鎖技術執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- 多執行緒與併發----讀寫鎖執行緒
- JAVA多執行緒併發Java執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- 多執行緒與高併發(一)多執行緒入門執行緒
- Java 多執行緒併發程式設計之互斥鎖 Reentrant LockJava執行緒程式設計
- Java多執行緒/併發08、中斷執行緒 interrupt()Java執行緒
- java併發與執行緒Java執行緒
- Java執行緒:執行緒的同步與鎖Java執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- java 多執行緒 併發 面試Java執行緒面試
- Java併發(十六)----執行緒八鎖Java執行緒
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- Java 併發和多執行緒(一) Java併發性和多執行緒介紹[轉]Java執行緒
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- 多執行緒併發篇——如何停止執行緒執行緒
- 【多執行緒與高併發】- 執行緒基礎與狀態執行緒
- 10、Java併發性和多執行緒-執行緒安全與不可變性Java執行緒
- Java多執行緒——執行緒Java執行緒
- Java多執行緒/併發12、多執行緒訪問static變數Java執行緒變數
- JAVA多執行緒與鎖機制Java執行緒
- Java多執行緒與併發之ThreadLocalJava執行緒thread
- Java高併發與多執行緒(一)-----概念Java執行緒
- Java多執行緒與併發 - 瞭解“monitor”Java執行緒
- 執行緒與多執行緒執行緒
- java多執行緒–同步鎖Java執行緒
- Java多執行緒-無鎖Java執行緒
- Java多執行緒學習(六)Lock鎖的使用Java執行緒
- Java併發和多執行緒:序Java執行緒
- Java多執行緒/併發07、Thread.Join()讓呼叫執行緒等待子執行緒Java執行緒thread
- 【多執行緒與高併發】Java守護執行緒是什麼?什麼是Java的守護執行緒?執行緒Java
- 23、Java併發性和多執行緒-重入鎖死Java執行緒
- 併發與多執行緒基礎執行緒