聽說你不會Lock,我發了3個夜晚寫給你
我們知道 synchronized 是java內部關鍵字,比較重量級的獨佔鎖,好處就是使用方便,不需要手動釋放鎖;然而
Lock 則需要手動加鎖,手動釋放鎖;
一ReentrantLock使用
ReentrantLock 意為可重入鎖,方法預覽如下
//建立一個 ReentrantLock 的例項 ReentrantLock() //建立一個具有給定公平策略的 ReentrantLock ReentrantLock(boolean fair) //查詢當前執行緒持有鎖的個數 int getHoldCount() //返回目前擁有此鎖的執行緒,如果此鎖不被任何執行緒擁有,則返回 null protected Thread getOwner() //返回一個collection,它包含可能正等待獲取此鎖的執行緒 protected Collection<Thread> getQueuedThreads() int getQueueLength() //返回正等待獲取此鎖的執行緒估計數 //返回一個 collection,它包含可能正在等待與此鎖相關給定條件的那些執行緒 protected Collection<Thread> getWaitingThreads(Condition condition) //返回等待與此鎖相關的給定條件的執行緒估計數 int getWaitQueueLength(Condition condition) //查詢給定執行緒是否正在等待獲取此鎖 boolean hasQueuedThread(Thread thread) //查詢是否有些執行緒正在等待獲取此鎖 boolean hasQueuedThreads() //查詢是否有些執行緒正在等待與此鎖有關的給定條件 boolean hasWaiters(Condition condition) //如果此鎖的公平設定為 true,則返回true boolean isFair() //查詢當前執行緒是否保持此鎖 boolean isHeldByCurrentThread() //查詢此鎖是否由任意執行緒保持 boolean isLocked() //獲取鎖 void lock() //如果當前執行緒未被中斷,則獲取鎖。 void lockInterruptibly() //返回用來與此 Lock 例項一起使用的 Condition 例項 Condition newCondition() //僅在呼叫時鎖未被另一個執行緒保持的情況下,才獲取該鎖 boolean tryLock() //如果鎖在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖 boolean tryLock(long timeout, TimeUnit unit) //釋放此鎖 void unlock()
1.1Lock與unLock 簡單使用
使用 lock() 加鎖, unlock()釋放鎖;
public class RLook { private Lock lock = new ReentrantLock(); private void testLock(){ // 加鎖 lock.lock(); // 執行業務邏輯 for (int i=0; i<8; i++){ System.out.println(i+"==="+Thread.currentThread().getName()); } // 釋放鎖 lock.unlock(); } public static void main(String[] args) { // 執行緒1 RLook rLook = new RLook(); new Thread(()-> { rLook.testLock(); }).start(); // 執行緒2 new Thread(()-> { rLook.testLock(); }).start(); } }
輸出如下,不同執行緒之間應是分組列印;
0===Thread-0
1===Thread-0
2===Thread-0
3===Thread-0
4===Thread-0
5===Thread-0
6===Thread-0
7===Thread-0
0===Thread-1
1===Thread-1
2===Thread-1
3===Thread-1
4===Thread-1
5===Thread-1
6===Thread-1
7===Thread-1
1.2Lock與unLock 正確使用方式
上面的程式碼有個缺點,如果在執行業務程式碼的時候發生了異常就會發生死鎖的現象,所以通常情況下我們會將業務程式碼放在try{},catch{} 程式碼塊中, 最後使用 finally 釋放鎖;
程式碼格式應如下
Lock lock =new ReentrantLock();
....
lock.lock();
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
....
1.3原始碼角度說明Lock
public interface Lock {
// 加鎖
void lock();
// 中斷
void lockInterruptibly() throws InterruptedException;
// 嘗試獲取鎖
boolean tryLock();
// 嘗試獲取鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 訊號通知
Condition newCondition();
}
-
lock()方法是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行鎖等待。採用Lock必須手動釋放鎖,並且在發生異常時,不會自動釋放鎖,所以需要放在try,cath程式碼塊中, 最後使用 finally 釋放鎖;
-
tryLock()方法用來表示嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗,則返回false;
-
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,區別在於可以設定鎖等待時間,在等待時間內為獲取到鎖,則返回false,獲取到則返回true;
Lock lock=new ReentrantLock();
....
if(lock.tryLock()) {
try{
//業務邏輯
}catch(Exception ex){
}finally{
//釋放鎖
lock.unlock();
}
}else {
// 未獲取鎖邏輯
}
- lockInterruptibly()為中斷方法,當通過這個方法去獲取鎖時,如果執行緒正在處於獲取鎖狀態,則該執行緒能夠響應中斷(中斷執行緒的等待狀態),丟擲中斷異常。也就使說,當兩個執行緒同時通過lock.lockInterruptibly()獲取某個鎖時,如果執行緒A獲取到了鎖,而執行緒B在等待狀態,那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待狀態;
- Condition類是JDK5的新API,可以實現多路通知功能,一個Lock物件可以建立多個Condition, 通過注入不同的Condition靈活實現不同的執行緒通知功能;然而synchronized 的 wait(), notify(), notify All() 通知機制是隨機無法實現選擇性通知功能;
1.4使用lockInterruptibly
public class InterruptTest {
private Lock lock = new ReentrantLock();
public void interrupt() throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "得到了鎖");
long startTime = System.currentTimeMillis();
for (; ; ) {
if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
break;
}
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "釋放鎖");
}
}
public static void main(String[] args) {
// 執行緒1
InterruptTest rLook = new InterruptTest();
Thread threadA = new Thread(() -> {
try {
rLook.interrupt();
} catch (InterruptedException e) {
System.out.println("執行緒A進行了中斷");
e.printStackTrace();
}
});
// 執行緒2
Thread threadB = new Thread(() -> {
try {
rLook.interrupt();
} catch (InterruptedException e) {
System.out.println("執行緒B進行了中斷");
e.printStackTrace();
}
});
threadA.setName("A");
threadB.setName("B");
threadA.start();
threadB.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.interrupt();
}
}
輸出結果如下,執行緒A通過中斷獲取鎖,執行緒B再通過中斷獲取鎖時處於等待狀態,直接中斷丟擲異常;
A得到了鎖
執行緒B進行了中斷
1.5使用Condition實現等待通知機制
codition 介面主要方法如下
//造成當前執行緒在接到訊號或被中斷之前一直處於等待狀態。
void await()
//造成當前執行緒在接到訊號、被中斷或到達指定等待時間之前一直處於等待狀態。
boolean await(long time, TimeUnit unit)
//造成當前執行緒在接到訊號、被中斷或到達指定等待時間之前一直處於等待狀態。
long awaitNanos(long nanosTimeout)
//造成當前執行緒在接到訊號之前一直處於等待狀態。
void awaitUninterruptibly()
//造成當前執行緒在接到訊號、被中斷或到達指定最後期限之前一直處於等待狀態。
boolean awaitUntil(Date deadline)
//喚醒一個等待執行緒。
void signal()
//喚醒所有等待執行緒。
void signalAll()
利用 Condition 的await() 方法和 signal() 方法 實現 等待通知機制;
public class ConditionTest {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 執行緒等待
private void await(){
try {
// 加鎖
lock.lock();
System.out.println("await時間為:"+System.currentTimeMillis());
// 等待
condition.await();
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放鎖
lock.unlock();
}
}
// 執行緒喚醒
public void signal() {
lock.lock();
try {
System.out.println("signal時間為" + System.currentTimeMillis());
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 執行緒1
ConditionTest rLook = new ConditionTest();
Thread thread = new Thread(() -> {
rLook.await();
});
thread.start();
//
thread.sleep(3000);
//
rLook.signal();
}
}
輸出輸出結果如下
await時間為:1606707478993
signal時間為1606707481993
多個Condition 使用方式
多個Condition 使用時,互不干涉; 執行緒 A, B 使用 不同Condition 的都 進入等待狀態, 當使用 對應的 Condition 的 singal 才喚醒對應的執行緒;
public class MultiCondition {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
// 執行緒等待
private void awaitA(){
try {
// 加鎖
lock.lock();
System.out.println("執行緒"+Thread.currentThread().getName()+"----await時間為:"+System.currentTimeMillis());
// 等待
conditionA.await();
System.out.println("執行緒"+Thread.currentThread().getName()+"繼續執行");
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放鎖
lock.unlock();
}
}
// 執行緒等待
private void awaitB(){
try {
// 加鎖
lock.lock();
System.out.println("執行緒"+Thread.currentThread().getName()+"----await時間為:"+System.currentTimeMillis());
// 等待
conditionB.await();
System.out.println("執行緒"+Thread.currentThread().getName()+"繼續執行");
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放鎖
lock.unlock();
}
}
// 執行緒喚醒
public void signalAllA() {
lock.lock();
try {
System.out.println("執行緒"+Thread.currentThread().getName()+"----signal時間為" + System.currentTimeMillis());
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 執行緒喚醒
public void signalAllB() {
lock.lock();
try {
System.out.println("執行緒"+Thread.currentThread().getName()+"----signal時間為" + System.currentTimeMillis());
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 執行緒1
MultiCondition rLook = new MultiCondition();
Thread threadA = new Thread(() -> {
rLook.awaitA();
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(() -> {
rLook.awaitB();
});
threadB.setName("B");
threadB.start();
//
Thread.sleep(3000);
//
rLook.signalAllA();
}
}
輸出結果如下,執行緒Condition A 對應的執行緒會被喚醒,B 執行緒 還是處於等待狀態;
執行緒A----await時間為:1606708895650
執行緒B----await時間為:1606708895650
執行緒main----signal時間為1606708898651
執行緒A繼續執行
1.6使用Condition實現生產消費模式
public class ConsumerProductCondition {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Boolean has = false;
// 生產
private void set(){
try {
// 加鎖
lock.lock();
while (has == true){
condition.await();
}
System.out.println("生產GG");
has = true;
// 喚醒一個執行緒
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放鎖
lock.unlock();
}
}
// 消費
private void get(){
try {
// 加鎖
lock.lock();
while (has == false){
condition.await();
}
System.out.println("消費MM");
has = false;
// 喚醒一個執行緒
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
// 釋放鎖
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 執行緒1
ConsumerProductCondition rLook = new ConsumerProductCondition();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 200; i++) {
rLook.set();
}
});
// 執行緒2
Thread threadB = new Thread(() -> {
for (int i = 0; i < 200; i++) {
rLook.get();
}
});
threadA.start();
threadB.start();
}
}
輸出交替列印
生產GG
消費MM
生產GG
消費MM
生產GG
消費MM
生產GG
消費MM
生產GG
消費MM
.....
1.7 公平鎖與非公平鎖
Lock 鎖分為公平鎖和非公平鎖,公平鎖表示通過執行緒加鎖的順序獲取鎖,非公平鎖表示通過搶佔機制獲取鎖;
公平鎖獲示例
public class FairLockTest {
private Lock lock = new ReentrantLock(true);
private void testLock(){
// 加鎖
lock.lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到鎖");
// 釋放鎖
lock.unlock();
}
public static void main(String[] args) {
// 執行緒1
FairLockTest rLook = new FairLockTest();
for (int i = 0; i <100 ; i++) {
// 執行緒2
Thread thread = new Thread(() -> {
rLook.testLock();
});
thread.setName(""+i);
thread.start();
}
}
}
輸出結果基本有序
執行緒0獲取到鎖
執行緒1獲取到鎖
執行緒2獲取到鎖
執行緒3獲取到鎖
執行緒4獲取到鎖
執行緒5獲取到鎖
執行緒6獲取到鎖
執行緒8獲取到鎖
執行緒7獲取到鎖
執行緒9獲取到鎖
執行緒10獲取到鎖
....
非公平鎖示例如下
public class FairLockTest {
private Lock lock = new ReentrantLock(false);
private void testLock(){
// 加鎖
lock.lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到鎖");
// 釋放鎖
lock.unlock();
}
public static void main(String[] args) {
// 執行緒1
FairLockTest rLook = new FairLockTest();
for (int i = 0; i <100 ; i++) {
// 執行緒2
Thread thread = new Thread(() -> {
rLook.testLock();
});
thread.setName(""+i);
thread.start();
}
}
}
輸出結果基本無序
執行緒0獲取到鎖
執行緒1獲取到鎖
執行緒7獲取到鎖
執行緒2獲取到鎖
執行緒10獲取到鎖
執行緒3獲取到鎖
執行緒6獲取到鎖
執行緒11獲取到鎖
執行緒4獲取到鎖
執行緒5獲取到鎖
.........
二 ReentrantReadWriteLock 使用
ReentrantLock 與 synchronized 都是 獨佔鎖, 安全性較高,但是相對來說效率低下;ReentrantReadWriteLock讀寫鎖提供了 readLock()和writeLock()用來獲取讀鎖和寫鎖; 讀鎖之間讀取資料不互斥(故讀鎖也成為共享鎖)。讀鎖寫鎖之間互斥,寫鎖寫鎖之間互斥;
2.1讀鎖
讀鎖示例
public class ReadLockTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void testLock(){
// 加鎖
lock.readLock().lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到鎖"+System.currentTimeMillis());
// 睡眠,保證B執行緒進來時A執行緒還是獲取鎖狀態
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 釋放鎖
lock.readLock().unlock();
}
public static void main(String[] args) {
// 執行緒1
ReadLockTest rLook = new ReadLockTest();
// 執行緒2
Thread threadA = new Thread(() -> {
rLook.testLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.testLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
執行緒A與執行緒B幾乎時同時獲取到鎖
執行緒B獲取到鎖1606723001641
執行緒A獲取到鎖1606723001641
2.2 寫鎖
寫鎖示例
public class WriteLockTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void testLock(){
// 加鎖
lock.writeLock().lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到鎖"+System.currentTimeMillis());
// 睡眠,保證B執行緒進來時A執行緒還是獲取鎖狀態
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 釋放鎖
lock.writeLock().unlock();
}
public static void main(String[] args) {
// 執行緒1
WriteLockTest rLook = new WriteLockTest();
// 執行緒2
Thread threadA = new Thread(() -> {
rLook.testLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.testLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
輸出結果基本就是相差 2 秒,即執行緒A獲取鎖後,執行緒B需要等執行緒A釋放鎖才能獲取鎖
執行緒A獲取到鎖1606723216664
執行緒B獲取到鎖1606723218664
2.3 讀寫鎖
讀寫鎖示例
public class ReadAndWriteTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void writeLock(){
// 加鎖
lock.writeLock().lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到寫鎖"+System.currentTimeMillis());
// 睡眠,保證B執行緒進來時A執行緒還是獲取鎖狀態
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 釋放鎖
lock.writeLock().unlock();
}
private void readLock(){
// 加鎖
lock.readLock().lock();
// 執行業務邏輯
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到讀鎖"+System.currentTimeMillis());
// 釋放鎖
lock.readLock().unlock();
}
public static void main(String[] args) {
// 執行緒1
ReadAndWriteTest rLook = new ReadAndWriteTest();
// 執行緒2
Thread threadA = new Thread(() -> {
rLook.writeLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.readLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
輸出結果如下,剛好相差2秒;當執行緒A獲取到寫鎖,執行緒B必須等待執行緒A釋放寫鎖後才能獲取到讀鎖;
執行緒A獲取到寫鎖1606724500217
執行緒B獲取到讀鎖1606724502217
三 總結
通過本篇文章,我們大概知道常見的鎖的使用方式;
- ReentrantLock 和 synchronized 都是 獨佔鎖,又是可重入鎖;獨佔鎖想必大家都知道,重點解釋下可重入鎖,
當執行緒A獲取到鎖進入 methodA 時,再次呼叫 methodB 就不需要再次獲取鎖,即代表可重入鎖;
class Test{
public synchronized void methodA() {
methodB();
}
public synchronized void methodB() {
}
}
-
synchronized不是可中斷鎖,而Lock是可中斷鎖。如果某一執行緒A正在執行鎖中的程式碼,執行緒B由於等待時間過長去處理其它業務,就稱執行緒B獲得的鎖為可中斷鎖;
-
公平鎖儘量以請求鎖的順序來獲取鎖。比如有多個執行緒在等待一個鎖,當這個鎖被釋放時,等待時間最久的執行緒(最先請求的執行緒)會獲得該鎖,這就是公平鎖;非公平鎖獲取方式就是完全隨機,搶佔模式;
-
讀寫鎖一般用於操作檔案,只要涉及到寫鎖都是獨佔鎖;讀鎖是共享鎖;
有關 其它鎖的知識,可以閱讀知識追尋者以前釋出的併發程式設計系列文章
歡迎關注我的公眾號:知識追尋者,送原創PDF,面經,開源系統