Java高階-執行緒同步lock與unlock使用
一、Lock與Synchronized區別
Java中可以使用Lock和Synchronized的可以實現對某個共享資源的同步,同時也可以實現對某些過程的原子性操作。
Lock可以使用Condition進行執行緒之間的排程,Synchronized則使用Object物件本身的notify, wait, notityAll排程機制,這兩種排程機制有什麼異同呢?
Condition是Java5以後出現的機制,它有更好的靈活性,而且在一個物件裡面可以有多個Condition(即物件監視器),則執行緒可以註冊在不同的Condition,從而可以有選擇性的排程執行緒,更加靈活。
Synchronized就相當於整個物件只有一個單一的Condition(即該物件本身)所有的執行緒都註冊在它身上,執行緒排程的時候之後排程所有得註冊執行緒,沒有選擇權,會出現相當大的問題 。
所以,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構,可以具有差別很大的屬性,可以支援多個相關的 Condition 物件。
鎖是控制多個執行緒對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨佔訪問。一次只能有一個執行緒獲得鎖,對共享資源的所有訪問都需要首先獲得鎖。不過,某些鎖可能允許對共享資源併發訪問,如 ReadWriteLock 的讀取鎖。
synchronized 方法或語句的使用提供了對與每個物件相關的隱式監視器鎖的訪問,但卻強制所有鎖獲取和釋放均要出現在一個塊結構中:當獲取了多個鎖時,它們必須以相反的順序釋放,且必須在與所有鎖被獲取時相同的詞法範圍內釋放所有鎖。
雖然 synchronized 方法和語句的範圍機制使得使用監視器鎖程式設計方便了很多,而且還幫助避免了很多涉及到鎖的常見程式設計錯誤,但有時也需要以更為靈活的方式使用鎖。例如,某些遍歷併發訪問的資料結果的演算法要求使用 "hand-over-hand" 或 "chain locking":獲取節點 A 的鎖,然後再獲取節點 B 的鎖,然後釋放 A 並獲取 C,然後釋放 B 並獲取 D,依此類推。Lock 介面的實現允許鎖在不同的作用範圍內獲取和釋放,並允許以任何順序獲取和釋放多個鎖,從而支援使用這種技術。
二、java.util.concurrent.locks類結構
上圖中,LOCK的實現類其實都是構建在AbstractQueuedSynchronizer上,為何圖中沒有用UML線表示呢,這是每個Lock實現類都持有自己內部類Sync的例項,而這個Sync就是繼承AbstractQueuedSynchronizer(AQS)。為何要實現不同的Sync呢?這和每種Lock用途相關。另外還有AQS的State機制。
基於AQS構建的Synchronizer包括ReentrantLock,Semaphore,CountDownLatch, ReetrantReadWriteLock,FutureTask等,這些Synchronizer實際上最基本的東西就是原子狀態的獲取和釋放,只是條件不一樣而已。
ReentrantLock:需要記錄當前執行緒獲取原子狀態的次數,如果次數為零,那麼就說明這個執行緒放棄了鎖(也有可能其他執行緒佔據著鎖從而需要等待),如果次數大於1,也就是獲得了重進入的效果,而其他執行緒只能被park住,直到這個執行緒重進入鎖次數變成0而釋放原子狀態。以下為ReetranLock的FairSync的tryAcquire實現程式碼解析:
Semaphore:則是要記錄當前還有多少次許可可以使用,到0,就需要等待,也就實現併發量的控制,Semaphore一開始設定許可數為1,實際上就是一把互斥鎖。以下為Semaphore的FairSync實現:
CountDownLatch:閉鎖則要保持其狀態,在這個狀態到達終止態之前,所有執行緒都會被park住,閉鎖可以設定初始值,這個值的含義就是這個閉鎖需要被countDown()幾次,因為每次CountDown是sync.releaseShared(1),而一開始初始值為10的話,那麼這個閉鎖需要被countDown()十次,才能夠將這個初始值減到0,從而釋放原子狀態,讓等待的所有執行緒通過。
FutureTask:需要記錄任務的執行狀態,當呼叫其例項的get方法時,內部類Sync會去呼叫AQS的acquireSharedInterruptibly()方法,而這個方法會反向呼叫Sync實現的tryAcquireShared()方法,即讓具體實現類決定是否讓當前執行緒繼續還是park,而FutureTask的tryAcquireShared方法所做的唯一事情就是檢查狀態,如果是RUNNING狀態那麼讓當前執行緒park。而跑任務的執行緒會在任務結束時呼叫FutureTask 例項的set方法(與等待執行緒持相同的例項),設定執行結果,並且通過unpark喚醒正在等待的執行緒,返回結果。
以上4個AQS的使用是比較典型,然而有個問題就是這些狀態存在哪裡呢?並且是可以計數的。從以上4個example,我們可以很快得到答案,AQS提供給了子類一個int state屬性。並且暴露給子類getState()和setState()兩個方法(protected)。這樣就為上述狀態解決了儲存問題,RetrantLock可以將這個state用於儲存當前執行緒的重進入次數,Semaphore可以用這個state儲存許可數,CountDownLatch則可以儲存需要被countDown的次數,而Future則可以儲存當前任務的執行狀態(RUNING,RAN,CANCELL)。其他的Synchronizer儲存他們的一些狀態。
AQS留給實現者的方法主要有5個方法,其中tryAcquire,tryRelease和isHeldExclusively三個方法為需要獨佔形式獲取的synchronizer實現的,比如執行緒獨佔ReetranLock的Sync,而tryAcquireShared和tryReleasedShared為需要共享形式獲取的synchronizer實現。
ReentrantLock內部Sync類實現的是tryAcquire,tryRelease, isHeldExclusively三個方法(因為獲取鎖的公平性問題,tryAcquire由繼承該Sync類的內部類FairSync和NonfairSync實現)Semaphore內部類Sync則實現了tryAcquireShared和tryReleasedShared(與CountDownLatch相似,因為公平性問題,tryAcquireShared由其內部類FairSync和NonfairSync實現)。CountDownLatch內部類Sync實現了tryAcquireShared和tryReleasedShared。FutureTask內部類Sync也實現了tryAcquireShared和tryReleasedShared。
其實使用過一些JAVA synchronizer的之後,然後結合程式碼,能夠很快理解其到底是如何做到各自的特性的,在把握了基本特性,即獲取原子狀態和釋放原子狀態,其實我們自己也可以構造synchronizer。如下是一個LOCK API的一個例子,實現了一個先入先出的互斥鎖。
三、lock與unlock使用
下面是一個場景,針對這個場景提出兩種解決方案。 一箇中轉站,可以接納貨物,然後發出貨物,這是需要建一個倉庫,相當於一個緩衝區,當倉庫滿的時候,不能接貨,倉庫空的時候,不能發貨。
第一種,用一個Condition去解決,有可能會出問題。
package com.zxx;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/**
* 單個Condition去控制一個緩衝區,多執行緒對緩衝區做讀寫操作,要保證緩衝區滿的時侯不會
* 被寫,空的時候不會被讀;單個Condition控制會出錯誤: 當緩衝區還有一個位置時,多個寫執行緒
* 同時訪問,則只有一個寫執行緒可以對其進行寫操作,操作完之後,喚醒在這個condition上等待的
* 其他幾個寫執行緒,如果判斷用IF語句的話就會出現繼續向緩衝區新增。
* @author Administrator
*
*/
public class ConditionError {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
String[] container = new String[10];
int index = 0;
public static void main(String[] args) {
ConditionError conditionError = new ConditionError();
conditionError.test();
}
public void test(){
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i = 0; i < 14; i++){//先用14個執行緒去寫,則有4個執行緒會被阻塞
threadPool.execute(new Runnable(){@Override
public void run() {
put();
}
});
}
Executors.newSingleThreadExecutor().execute(new Runnable(){//用一個執行緒去取,則會通知4個阻塞的寫執行緒工作,此時
//會有一個執行緒向緩衝區寫,寫完後去通知在這個condition上等待
//的取執行緒,這是它的本意,但是它喚醒了寫執行緒,因為只有一個condition
//不能有選擇的喚醒寫取執行緒,此時就需要有多個Condition
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
get();
}
});
}
/**
* 向緩衝去寫資料
*/
public void put(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "當前位置:" + index + "-----------------------------");
while(index == 10){
try {
System.out.println(Thread.currentThread().getName() + "處於阻塞狀態!");
condition.await();
// index = 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
container[index] = new String(new Random().nextInt() + "");
condition.signalAll();
index ++;
} finally {
lock.unlock();
}
}
/**
* 從緩衝區拿資料
*/
public void get(){
lock.lock();
try{
while(index == 0){
try {
System.out.println("get--------" + Thread.currentThread().getName() + "處於阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index --;
System.out.println("get---------" + Thread.currentThread().getName() + "喚醒阻塞");
condition.signalAll();
} finally {
lock.unlock();
}
}
}
第二種解決方案,用java api中的 一個例子 。
相關文章
- 6.JUC執行緒高階-Lock同步鎖執行緒
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- Java執行緒:執行緒的同步與鎖Java執行緒
- 《Java 高階篇》七:執行緒和執行緒池Java執行緒
- java執行緒安全LockJava執行緒
- Java執行緒池和Spring非同步處理高階篇Java執行緒Spring非同步
- Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLockJava執行緒
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- Java—執行緒同步Java執行緒
- Java 高階 --- 多執行緒快速入門Java執行緒
- java執行緒同步:synchronized關鍵字,Lock介面以及可重Java執行緒synchronized
- 執行緒與同步非同步執行緒非同步
- java執行緒學習5——執行緒同步之同步方法Java執行緒
- java 多執行緒 –同步Java執行緒
- java 多執行緒 --同步Java執行緒
- Java 執行緒與同步的效能優化Java執行緒優化
- Java多執行緒學習(六)Lock鎖的使用Java執行緒
- 執行緒同步C#關鍵字:lock,monitor執行緒C#
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- Java中的執行緒同步Java執行緒
- Java 執行緒同步原理探析Java執行緒
- java多執行緒–同步鎖Java執行緒
- java基礎:執行緒同步Java執行緒
- Java高階-解析Java中的多執行緒機制(轉)Java執行緒
- JUC執行緒高階---執行緒控制通訊Condition執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- 非同步與執行緒阻塞非同步執行緒
- @Java | Thread & synchronized – [ 執行緒同步鎖 基本使用]Javathreadsynchronized執行緒
- 進階Java多執行緒Java執行緒
- Java執行緒池進階Java執行緒
- Java高併發與多執行緒(一)-----概念Java執行緒
- Java與執行緒Java執行緒
- Java多執行緒—執行緒同步(單訊號量互斥)Java執行緒
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- 簡單的執行緒同步問題:兩個執行緒交替執行N次【Synchronized、Lock、ArrayBlockingQueue】執行緒synchronizedBloC
- Android小知識-Java多執行緒相關(Lock使用)AndroidJava執行緒
- java synchronize - 執行緒同步機制Java執行緒