本章內容涵蓋Lock的使用講解,可重入鎖、讀寫鎖。Lock和Synchronized的對比等。
多線程一直Java開發中的難點,也是面試中的常客,趁着還有時間,打算鞏固一下JUC方面知識,我想機會隨處可見,但始終都是留給有准備的人的,希望我們都能加油!!!
沉下去,再浮上來
,我想我們會變的不一樣的。
一、什么是 Lock
Lock 鎖實現提供了比使用同步方法和語句可以獲得的更廣泛的鎖操作。
二、鎖類型
可重入鎖:在執行對象中所有同步方法不用再次獲得鎖
可中斷鎖:在等待獲取鎖過程中可中斷
公平鎖: 按等待獲取鎖的線程的等待時間進行獲取,等待時間長的具有優先獲取鎖權利
讀寫鎖:對資源讀取和寫入的時候拆分為2部分處理,讀的時候可以多線程一起讀,寫的時候必須同步地寫
三、Lock接口
public interface Lock {
void lock(); //獲得鎖。
/**
除非當前線程被中斷,否則獲取鎖。
如果可用,則獲取鎖並立即返回。
如果鎖不可用,則當前線程將出於線程調度目的而被禁用並處於休眠狀態,直到發生以下兩種情況之一:
鎖被當前線程獲取;
要么其他一些線程中斷當前線程,支持中斷獲取鎖。
如果當前線程:
在進入此方法時設置其中斷狀態;
要么獲取鎖時中斷,支持中斷獲取鎖,
*/
void lockInterruptibly() throws InterruptedException;
/**
僅在調用時空閑時才獲取鎖。
如果可用,則獲取鎖並立即返回值為true 。 如果鎖不可用,則此方法將立即返回false值。
*/
boolean tryLock();
//比上面多一個等待時間
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解鎖
void unlock();
//返回綁定到此Lock實例的新Condition實例。
Condition newCondition(); 。
}
下面講幾個常用方法的使用。
3.1、lock()、unlock()
lock()是最常用的方法之一,作用就是獲取鎖,如果鎖已經被其他線程獲得,則當前線程將被禁用以進行線程調度,並處於休眠狀態,等待,直到獲取鎖。
如果使用到了lock的話,那么必須去主動釋放鎖,就算發生了異常,也需要我們主動釋放鎖,因為lock並不會像synchronized一樣被自動釋放。所以使用lock的話,必須是在try{}catch(){}
中進行,並將釋放鎖的代碼放在finally{}
中,以確保鎖一定會被釋放,以防止死鎖現象的發生。
unlock()的作用就是主動釋放鎖。
lock接口的類型有好幾個實現類,這里是隨便找了個哈。
Lock lock = new ReentrantLock();
try {
lock.lock();
System.out.println("上鎖了");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("解鎖了");
}
3.2、newCondition
關鍵字 synchronized 與 wait()/notify()這兩個方法一起使用可以實現等待/通知模式, Lock 鎖的 newContition()方法返回 Condition 對象,Condition 類 也可以實現等待/通知模式。 用 notify()通知時,JVM 會隨機喚醒某個等待的線程, 使用 Condition 類可以 進行選擇性通知, Condition 比較常用的兩個方法:
- await():會使當前線程等待,同時會釋放鎖,當等到其他線程調用
signal()
方法時,此時這個沉睡線程會重新獲得鎖並繼續執行代碼(在哪里沉睡就在哪里喚醒)。 - signal():用於喚醒一個等待的線程。
注意
:在調用 Condition 的 await()/signal()方法前,也需要線程持有相關 的 Lock 鎖,調用 await()后線程會釋放這個鎖,在調用singal()方法后會從當前 Condition對象的等待隊列中,喚醒一個線程,后被喚醒的線程開始嘗試去獲得鎖, 一旦成功獲得鎖就繼續往下執行。
在這個地方我們舉個例子來用代碼寫一下哈:
這里就不舉例synchronized 實現了,道理都差不多。
例子:我們有兩個線程,實現對一個初始值是0的number變量,一個線程當number = =0時 對number值+1,另外一個線程當number = = 1時對number-1。
class Share {
private Integer number = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition newCondition = lock.newCondition();
// +1 的方法
public void incr() {
try {
lock.lock(); // 加鎖
while (number != 0) {
newCondition.await();//沉睡
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
newCondition.signal(); //喚醒另一個沉睡的線程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1 的方法
public void decr() {
try {
lock.lock();
while (number != 1) {
newCondition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
newCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i=0;i<=10;i++){
share.incr();
}
},"AA").start();
new Thread(()->{
for (int i=0;i<=10;i++){
share.decr();
}
},"BB").start();
/**
* AA::1
* BB::0
* AA::1
* BB::0
* .....
*/
}
}
四、ReentrantLock (可重入鎖)
ReentrantLock
,意思是“可重入鎖”。ReentrantLock 是唯一實現了 Lock 接口的類,並且 ReentrantLock 提供了更 多的方法。
可重入鎖
:什么是 “可重入”,可重入就是說某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖。
package com.crush.juc02;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("第1次獲取鎖,這個鎖是:" + lock);
for (int i = 2;i<=11;i++){
try {
lock.lock();
System.out.println("第" + i + "次獲取鎖,這個鎖是:" + lock);
try {
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();// 如果把這里注釋掉的話,那么程序就會陷入死鎖當中。
}
}
} finally {
lock.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("這里是為了測試死鎖而多寫一個的線程");
} finally {
lock.unlock();
}
}
}).start();
}
}
/**
* 第1次獲取鎖,這個鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]
* 第2次獲取鎖,這個鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]
* 第3次獲取鎖,這個鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]
* ...
*/
死鎖的話,程序就無法停止,直到資源耗盡或主動終止。
代碼中也稍微提了一下死鎖的概念,在使用Lock中必須手動解鎖
,不然就會可能造成死鎖的現象。
五、ReadWriteLock (讀寫鎖)
ReadWriteLock 也是一個接口,在它里面只定義了兩個方法:
public interface ReadWriteLock {
// 獲取讀鎖
Lock readLock();
// 獲取寫鎖
Lock writeLock();
}
分為一個讀鎖一個寫鎖,將讀寫進行了分離,使可以多個線程進行讀操作,從而提高了效率。
ReentrantReadWriteLock 實現了 ReadWriteLock 接口。里面提供了更豐富的方法,當然最主要的還是獲取寫鎖(writeLock)和讀鎖(readLock)。
5.1、案例
假如多個線程要進行讀的操作,我們用Synchronized 來實現的話。
public class SynchronizedDemo2 {
public static void main(String[] args) {
final SynchronizedDemo2 test = new SynchronizedDemo2();
new Thread(()->{
test.get(Thread.currentThread());
}).start();
new Thread(()->{
test.get(Thread.currentThread());
}).start();
}
public synchronized void get(Thread thread) {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在進行讀操作");
}
System.out.println(thread.getName()+"讀操作完畢");
}
}
/**
* 輸出
* Thread-0正在進行讀操作
* Thread-0讀操作完畢
* Thread-1正在進行讀操作
* Thread-1正在進行讀操作
* Thread-1正在進行讀操作
* ....
* Thread-1讀操作完畢
*/
改成讀寫鎖之后
public class SynchronizedDemo2 {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final SynchronizedDemo2 test = new SynchronizedDemo2();
new Thread(()->{
test.get2(Thread.currentThread());
}).start();
new Thread(()->{
test.get2(Thread.currentThread());
}).start();
}
public void get2(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在進行讀操作");
}
System.out.println(thread.getName()+"讀操作完畢");
} finally {
rwl.readLock().unlock();
}
}
}
/**
* 輸出
* Thread-0正在進行讀操作
* Thread-0讀操作完畢
* Thread-1正在進行讀操作
* Thread-1讀操作完畢
*/
結論:改用讀寫鎖后 線程1和線程2 同時在讀,可以感受到效率的明顯提升。
注意
:
- 若此時已經有一個線程占用了讀鎖,此時其他線程申請讀鎖是可以的,但是若此時其他線程申請寫鎖,則只有等待讀鎖釋放,才能成功獲得。
- 若此時已經有一個線程占用了寫鎖,那么此時其他線程申請寫鎖或讀鎖,都只有持有寫鎖的線程釋放寫鎖,才能成功獲得。
六、Lock 與的 Synchronized 區別
類別 | synchronized | Lock |
---|---|---|
存在層次 | Java的關鍵字,在jvm層面上 | 是一個接口 |
鎖的獲取 | 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 | 分情況而定,Lock有多個鎖獲取的方式,具體下面會說道,大致就是可以嘗試獲得鎖,線程可以不用一直等待 |
鎖的釋放 | 1、當 synchronized 方法或者 synchronized 代碼塊執行完之后, 系統會自動讓線程釋放對鎖的占用 (不需要手動釋放鎖)2、若線程執行發生異常,jvm會讓線程釋放鎖 | 在finally中必須釋放鎖,不然容易造成線程死鎖現象 (需要手動釋放鎖) |
鎖狀態 | 無法判斷 | 可以判斷 |
鎖類型 | 鎖類型 | 可重入 可判斷 可公平(兩者皆可) |
性能 | 前提:大量線程情況下 同步效率較低 | 前提:大量線程情況下 同步效率比synchronized高的多 |
Lock可以提高多個線程進行讀操作的效率。
七、自言自語
最近又開始了JUC的學習,感覺Java內容真的很多,但是為了能夠走的更遠,還是覺得應該需要打牢一下基礎。
正在持續更新中,如果你覺得對你有所幫助,也感興趣的話,關注我吧,讓我們一起學習,一起討論吧。
你好,我是博主寧在春
,Java學習路上的一顆小小的種子,也希望有一天能扎根長成蒼天大樹。
希望與君共勉
😁
待我們,別時相見時,都已有所成。