Java中的ReentrantLock解讀
ReentrantLock
ReentantLock 是java中重入鎖的實現,一次只能有一個線程來持有鎖,包含三個內部類, Sync 、 NonFairSync 、 FairSync 。
1、構造函數
無參構造,默認使用的是非公平性鎖
public ReentrantLock() {
sync = new NonfairSync();
}有參構造, Boolean類型的參數 true:表示公平性鎖 false:非公平性鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}reentantlock是lock接口的實現類,即實現了Lock接口下所有的方法; 獲取鎖的方法lock、trylock、lockintertuptibly加鎖方式以及釋放鎖方法。
2、公平性鎖和非公平性鎖
(1)公平性鎖和非公平性鎖示例
NonFairAndFairDemo類
import java.util.concurrent.locks.ReentrantLock;
public class NonFairAndFairDemo implements Runnable {
//靜態(tài)變量(線程共享)
private static int num = 0;
//鎖實例
private ReentrantLock rtl;
public NonFairAndFairDemo(ReentrantLock rtl) {
this.rtl = rtl;
}
@Override
public void run() {
while (true) {
//加鎖
rtl.lock();
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
rtl.unlock();
}
}
}測試公平鎖
@Test
public void test01() {
ReentrantLock reentrantLock = new ReentrantLock(true);
Thread threadA = new Thread(new NonFairAndFairDemo(reentrantLock));
threadA.setName("A");
Thread threadB = new Thread(new NonFairAndFairDemo(reentrantLock));
threadB.setName("B");
threadA.start();
threadB.start();
}執(zhí)行結果

公平向鎖特征如上:按照線程的訪問順序進行獲取。
測試非公平鎖
@Test
public void test02() {
ReentrantLock reentrantLock = new ReentrantLock(false);
Thread threadA = new Thread(new NonFairAndFairDemo(reentrantLock));
threadA.setName("A");
Thread threadB = new Thread(new NonFairAndFairDemo(reentrantLock));
threadB.setName("B");
threadA.start();
threadB.start();
}執(zhí)行結果

非公平性鎖的特點,是每個線程都連續(xù)執(zhí)行多次之后在替換成其他線程執(zhí)行。
(2)公平鎖和非公平鎖的實現
abstract static class Sync extends AbstractQueuedSynchronizer
公平性鎖和非公平性鎖的父類是 sync , sync 類是 AbstractQueuedSynchronizer 是其子類,AQS是一個同步器,提供同步功能。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//加鎖操作,聲明是抽象方法,nofairsync和fairsync中各自實現
abstract void lock();
//非公平獲取,公平性鎖和非公平性鎖都需要這個方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//AQS獲取state值
int c = getState();
if (c == 0) {
//鎖空閑狀態(tài)
//通過cas獲取鎖狀態(tài),修改state狀態(tài)
if (compareAndSetState(0, acquires)) {
//標記當前線程為獲取鎖的線程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//鎖非空閑,表明鎖被占中,有一種情況,當前線程即為占用鎖的線程
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//當前線程繼續(xù)持有鎖,僅對state進行加操作
setState(nextc);
return true;
}
return false;
}
//釋放鎖 sync中的tryRelease是公平性鎖和非公平性鎖的釋放鎖流程都是該方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只有持有鎖的線程才能釋放鎖
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//鎖才會被釋放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//判斷當前線程是否持有鎖
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
//獲取鎖的持有者線程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//加鎖的次數
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//是否上鎖 true:表示加鎖
final boolean isLocked() {
return getState() != 0;
}
}該Sync中方法的封裝是調用AQS中的方法實現的
公平性鎖和非公平鎖的如何實現?
公平性鎖:FairLock
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//同步狀態(tài)為空閑,需要等待隊列中第一個等待著執(zhí)行
//什么時候當前線程可以執(zhí)行? 等待隊列里沒有線程等待或者是有線程等待且等待的第一個線程就是當前線程
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//當前同步狀態(tài)非空閑,被線程占用且是當前線程
int nextc = c + acquires;
if (nextc < 0)
//有符號的int類型。最高位為1表示負數
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
類AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
//當前同步狀態(tài)非空閑,并且是其他線程持有鎖 返回false
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}公平性鎖獲取鎖流程:
1、如果同步狀態(tài)為空,就可以搶鎖,能夠獲取鎖的前提條件是當前等待隊列為空,或者等待隊列隊頭是當前線程,即當前線程才能夠搶鎖,通過CAS搶鎖(state)搶鎖成功記錄當前線程信息到鎖上。
2、如果同步狀態(tài)不為空,即存在線程占用鎖且占用線程是當前線程,當前線程可成功獲取鎖(state)。
非公平性鎖
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//執(zhí)行Lock操作,嘗試立即獲取鎖,失敗就退回常規(guī)流程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//立即獲取鎖失敗進入到acquire,首先調用tryAcquire
}
protected final boolean tryAcquire(int acquires) {
//同步狀態(tài)為空閑或者不為空閑但是是當前線程持有鎖,返回true表示搶鎖成功
return nonfairTryAcquire(acquires);
}
}
類AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
//當前同步狀態(tài)非空閑,并且是其他線程持有鎖 返回false
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}通過代碼可知:很多方法,trylock,unlock都是在父類sync實現
非公平性鎖搶鎖流程:
1、直接通過CAS操作搶鎖,如果不成功進入常規(guī)搶鎖流程。
2、獲取當前鎖的狀態(tài)(state是否為0),如果為0表示空閑,直接通過CAS搶鎖,如果成功,記錄線程信息到鎖上。
3、如果鎖不為空閑且是當前線程持有鎖,則可直接獲取鎖(state+1)。
重入鎖的實現: ReentrantLock 都是將具體實現委托給內部類(Sync、NonFairSync、FairSync)。 ReentrantLock 的重入次數是使用AQS的state屬性,state大于0表示鎖被占用(值表示當前線程重入次數),等于0表示鎖空閑,小于0則表示重入次數太多導致溢出了。 可重入鎖需要一個重入計數的變量,初始值為0,當成功請求鎖加1,釋放鎖時減1,當釋放鎖時計數為0則真正釋放鎖,重入鎖必須持有對鎖持有者的引用,用以判斷是否可以重入。
(3)Condition
synchronized與wait、notify、notifyAll方法結合可以實現等待/通知模式。reentantlock同樣可以實現等待、通知模式,需要借助于Condition對象,具有更好的靈活性。
newCondition方法
public Condition newCondition()
Condition中提供的方法如下:

awaitXXX和Object中的wait方法類似,使當前線程進入休眠等待, signal和Object中的notify方法類似,喚醒一個處于休眠狀態(tài)的線程 signalAll和Object中的signalAll方法類似,喚醒所有處于休眠狀態(tài)的線程。
生產者和消費者
生產者
public class Producer extends Thread {
private LinkedList<Integer> cap;//共享倉庫
private Random random = new Random();
private ReentrantLock rlk;
private Condition pToc;
private Condition cTop;
public Producer(LinkedList<Integer> cap, ReentrantLock rlk, Condition pToc, Condition cTop) {
this.cap = cap;
this.rlk = rlk;
this.pToc = pToc;
this.cTop = cTop;
}
@Override
public void run() {
while (true) {
rlk.lock();
try {
if (cap.size() == 3) {//緩沖區(qū)滿 生產者進行阻塞
System.out.println("緩沖區(qū)滿");
cTop.await();
}
//生產產品
int i = random.nextInt(1000);
System.out.println("生產者生產了" + i);
cap.add(i);
//通知消費者消費產品
pToc.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
rlk.unlock();
}
}
}消費者
public class Consumer extends Thread {
private LinkedList<Integer> cap;//共享倉庫
private ReentrantLock rlk;
private Condition pToc;
private Condition cTop;
public Consumer(LinkedList<Integer> cap, ReentrantLock rlk, Condition pToc, Condition cTop) {
this.cap = cap;
this.rlk = rlk;
this.pToc = pToc;
this.cTop = cTop;
}
@Override
public void run() {
while (true) {
rlk.lock();
try {
if (cap.size() == 0) { //如果緩沖區(qū)為0,消費者阻塞
System.out.println("緩沖區(qū)為空");
pToc.await();
}
//消費者消費產品
Integer i = cap.remove();
System.out.println("消費者消費了" + i);
//通知生產者生產
cTop.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
rlk.unlock();
}
}
}測試類
public class Test {
public static void main(String[] args) {
LinkedList<Integer> cap = new LinkedList<>();
ReentrantLock reentrantLock = new ReentrantLock();
//生產者通知消費者
Condition pToc = reentrantLock.newCondition();
//消費者通知生產者
Condition cTop = reentrantLock.newCondition();
Producer producer = new Producer(cap,reentrantLock,pToc,cTop);
Consumer consumer = new Consumer(cap,reentrantLock,pToc,cTop);
producer.start();
consumer.start();
}
}執(zhí)行結果

- 在調用Condition中的await或者是signal這些方法中任何的方法時,必須持有鎖(ReentantLock),如果沒有持有此鎖,則拋出IllegalMonitorStateException異常。
- 在調用await方法時,將釋放掉鎖,并在這些方法返回之前,重新先獲取該鎖,才能執(zhí)行。
- 如果線程在等待中被中斷,則等待將終止,并拋出InterruptedException,清除掉中斷狀態(tài)。
- 等待狀態(tài)的線程按照FIFO順序接收信號。
- 等待方法返回的線程重新獲取鎖的順序與線程最初獲取鎖的順序是相同的。
循環(huán)打印ABC
ABCThread類
public class ABCThread extends Thread {
private String name;
private ReentrantLock rtl;
private Condition waitc;//等待Condition
private Condition sigalc; //通知Condition
public ABCThread(String name,ReentrantLock rtl,Condition wc,Condition sc){
this.name = name;
this.rtl = rtl;
this.waitc = wc;
this.sigalc = sc;
}
@Override
public void run() {
int num =0;
while (true) {
rtl.lock();
//等待其他線程通知,
try {
waitc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印當前線程信息
System.out.println(name);
//通知下一個線程
sigalc.signal();
++num;
if (num >= 10) break;
rtl.unlock();
}
}
}測試
@Test
public void test() {
ReentrantLock reentrantLock = new ReentrantLock();
//A通知B
Condition ab = reentrantLock.newCondition();
//B通知C
Condition bc = reentrantLock.newCondition();
//C通知A
Condition ca = reentrantLock.newCondition();
new ABCThread("A", reentrantLock, ca, ab).start();
new ABCThread("B", reentrantLock, ab, bc).start();
new ABCThread("C", reentrantLock, bc, ca).start();
//先發(fā)起通知A線程
reentrantLock.lock();
ca.signal();
reentrantLock.unlock();
}執(zhí)行結果

到此這篇關于Java中的ReentrantLock解讀的文章就介紹到這了,更多相關Java的ReentrantLock內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
新手小白看過來學JAVA必過IO流File字節(jié)流字符流
這篇文章主要介紹了新手小白學JAVA到IO流File字節(jié)流字符流的重點,對流不清楚的新手同學快進來學習吧,大佬也可以進來溫故一下2021-08-08
Java多線程環(huán)境下SimpleDateFormat類安全轉換
這篇文章主要介紹了Java多線程環(huán)境下SimpleDateFormat類安全轉換,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-02-02
淺談spring中的default-lazy-init參數和lazy-init
下面小編就為大家?guī)硪黄獪\談spring中的default-lazy-init參數和lazy-init。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04

