Java多線程 ReentrantLock互斥鎖詳解
加鎖和解鎖
我們來看下ReentrantLock的基本用法
ThreadDomain35類
public class ThreadDomain35 {
private Lock lock = new ReentrantLock();
public void testMethod()
{
try
{
lock.lock();
for (int i = 0; i < 2; i++)
{
System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i = " + i);
}
}
finally
{
lock.unlock();
}
}
}
線程和main方法
public class MyThread35 extends Thread {
private ThreadDomain35 td;
public MyThread35(ThreadDomain35 td)
{
this.td = td;
}
public void run()
{
td.testMethod();
}
public static void main(String[] args)
{
ThreadDomain35 td = new ThreadDomain35();
MyThread35 mt0 = new MyThread35(td);
MyThread35 mt1 = new MyThread35(td);
MyThread35 mt2 = new MyThread35(td);
mt0.start();
mt1.start();
mt2.start();
}
}
輸出結(jié)果
ThreadName = Thread-2, i = 0 ThreadName = Thread-2, i = 1 ThreadName = Thread-0, i = 0 ThreadName = Thread-0, i = 1 ThreadName = Thread-1, i = 0 ThreadName = Thread-1, i = 1
一個(gè)線程必須執(zhí)行完才能執(zhí)行下一個(gè)線程,說明ReentrantLock可以加鎖。
ReentrantLock持有的對象監(jiān)視器和synchronized不同
ThreadDomain37類,methodB用synchronized修飾
public class ThreadDomain37 {
private Lock lock = new ReentrantLock();
public void methodA()
{
try
{
lock.lock();
System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public synchronized void methodB()
{
System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
}
}
MyThread37_0類
public class MyThread37_0 extends Thread {
private ThreadDomain37 td;
public MyThread37_0(ThreadDomain37 td)
{
this.td = td;
}
public void run()
{
td.methodA();
}
}
MyThread37_1類
public class MyThread37_1 extends Thread {
private ThreadDomain37 td;
public MyThread37_1(ThreadDomain37 td)
{
this.td = td;
}
public void run()
{
td.methodB();
}
}
MyThread37_main方法
public class MyThread37_main {
public static void main(String[] args)
{
ThreadDomain37 td = new ThreadDomain37();
MyThread37_0 mt0 = new MyThread37_0(td);
MyThread37_1 mt1 = new MyThread37_1(td);
mt0.start();
mt1.start();
}
}
運(yùn)行結(jié)果如下
MethodA begin ThreadName = Thread-0 MethodB begin ThreadName = Thread-1 MethodB begin ThreadName = Thread-1 MethodA end ThreadName = Thread-0
加了synchronized依然是異步執(zhí)行,說明ReentrantLock和synchronized持有的對象監(jiān)視器不同。ReentrantLock需要手動(dòng)加鎖和釋放鎖。
Condition
基本用法
synchronized與wait()和nitofy()/notifyAll()方法可以實(shí)現(xiàn)等待/喚醒模型,ReentrantLock同樣可以,需要借助Condition的await()和signal/signalAll(),await()釋放鎖。
ThreadDomain38類
public class ThreadDomain38 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await()
{
try
{
lock.lock();
System.out.println("await時(shí)間為:" + System.currentTimeMillis());
condition.await();
System.out.println("await等待結(jié)束");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void signal()
{
try
{
lock.lock();
System.out.println("signal時(shí)間為:" + System.currentTimeMillis());
condition.signal();
System.out.println("signal等待結(jié)束");
}
finally
{
lock.unlock();
}
}
}
MyThread38類,線程和main方法
public class MyThread38 extends Thread
{
private ThreadDomain38 td;
public MyThread38(ThreadDomain38 td)
{
this.td = td;
}
public void run()
{
td.await();
}
public static void main(String[] args) throws Exception
{
ThreadDomain38 td = new ThreadDomain38();
MyThread38 mt = new MyThread38(td);
mt.start();
Thread.sleep(3000);
td.signal();
}
}
運(yùn)行結(jié)果如下
await時(shí)間為:1563505465346 signal時(shí)間為:1563505468345 signal等待結(jié)束 await等待結(jié)束
可以看到,ReentrantLock和Condition實(shí)現(xiàn)了等待/通知模型。
一個(gè)Lock可以創(chuàng)建多個(gè)Condition;
notify()喚醒的線程是隨機(jī)的,signal()可以有選擇性地喚醒。
Condition選擇 喚醒/等待
現(xiàn)在看一個(gè)利用Condition選擇等待和喚醒的例子
ThreadDomain47類,定義add和sub方法
public class ThreadDomain47 {
private final Lock lock = new ReentrantLock();
private final Condition addCondition = lock.newCondition();
private final Condition subCondition = lock.newCondition();
private static int num = 0;
private List<String> lists = new LinkedList<String>();
public void add() {
lock.lock();
try {
while(lists.size() == 10) {//當(dāng)集合已滿,則"添加"線程等待
addCondition.await();
}
num++;
lists.add("add Banana" + num);
System.out.println("The Lists Size is " + lists.size());
System.out.println("The Current Thread is " + "增加線程");
System.out.println("==============================");
this.subCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {//釋放鎖
lock.unlock();
}
}
public void sub() {
lock.lock();
try {
while(lists.size() == 0) {//當(dāng)集合為空時(shí),"減少"線程等待
subCondition.await();
}
String str = lists.get(0);
lists.remove(0);
System.out.println("The Token Banana is [" + str + "]");
System.out.println("The Current Thread is " + "減少線程");
System.out.println("==============================");
num--;
addCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
MyThread40_0類,增加線程
public class MyThread40_0 implements Runnable {
private ThreadDomain47 task;
public MyThread40_0(ThreadDomain47 task) {
this.task = task;
}
@Override
public void run() {
task.add();
}
}
MyThread40_1類,減少線程
public class MyThread40_1 implements Runnable {
private ThreadDomain47 task;
public MyThread40_1(ThreadDomain47 task) {
this.task = task;
}
@Override
public void run() {
task.sub();
}
}
main方法,啟動(dòng)線程
public class MyThread40_main {
public static void main(String[] args) {
ThreadDomain47 task = new ThreadDomain47();
Thread t1=new Thread(new MyThread40_0(task));
Thread t3=new Thread(new MyThread40_0(task));
Thread t7=new Thread(new MyThread40_0(task));
Thread t8=new Thread(new MyThread40_0(task));
Thread t2 = new Thread(new MyThread40_1(task));
Thread t4 = new Thread(new MyThread40_1(task));
Thread t5 = new Thread(new MyThread40_1(task));
Thread t6 = new Thread(new MyThread40_1(task));
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
}
}
輸出結(jié)果如下
The Lists Size is 1 The Current Thread is 增加線程 ============================== The Lists Size is 2 The Current Thread is 增加線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減少線程 ============================== The Token Banana is [add Banana2] The Current Thread is 減少線程 ============================== The Lists Size is 1 The Current Thread is 增加線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減少線程 ============================== The Lists Size is 1 The Current Thread is 增加線程 ============================== The Token Banana is [add Banana1] The Current Thread is 減少線程 ==============================
可以看到,lists的數(shù)量不會(huì)增加太多,也不會(huì)減少太多。當(dāng)集合滿,使增加線程等待,喚醒減少線程;當(dāng)集合空,使減少線程等待,喚醒增加線程。我們用wait()/notify()機(jī)制無法實(shí)現(xiàn)該效果,這里體現(xiàn)了Condition的強(qiáng)大之處。
ReentrantLock中的方法
公平鎖和非公平鎖
ReentrantLock可以指定公平鎖和非公平鎖,公平鎖根據(jù)線程運(yùn)行的順序獲取鎖,非公平鎖則通過搶占獲得鎖,不按線程運(yùn)行順序。synchronized是非公平鎖。在ReentrantLock(boolean fair)構(gòu)造函數(shù)傳入true/false來指定公平鎖/非公平鎖。
看個(gè)例子
ThreadDomain39類和main方法
public class ThreadDomain39 {
private Lock lock = new ReentrantLock(true);
public void testMethod()
{
try
{
lock.lock();
System.out.println("ThreadName" + Thread.currentThread().getName() + "獲得鎖");
}
finally
{
lock.unlock();
}
}
public static void main(String[] args) throws Exception
{
final ThreadDomain39 td = new ThreadDomain39();
Runnable runnable = new Runnable()
{
public void run()
{
System.out.println("線程" + Thread.currentThread().getName() + "運(yùn)行了");
td.testMethod();
}
};
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < 5; i++)
threads[i].start();
}
}
輸出結(jié)果如下
線程Thread-0運(yùn)行了 ThreadNameThread-0獲得鎖 線程Thread-1運(yùn)行了 線程Thread-2運(yùn)行了 ThreadNameThread-1獲得鎖 線程Thread-3運(yùn)行了 線程Thread-4運(yùn)行了 ThreadNameThread-2獲得鎖 ThreadNameThread-3獲得鎖 ThreadNameThread-4獲得鎖
可以看到公平鎖獲得鎖的順序和線程運(yùn)行的順序相同。公平鎖盡可能地讓線程獲取鎖的順序和線程運(yùn)行順序保持一致,再執(zhí)行幾次,可能不一致。
ReentrantLock構(gòu)造函數(shù)傳入false,輸出結(jié)果如下:
線程Thread-0運(yùn)行了 線程Thread-2運(yùn)行了 線程Thread-4運(yùn)行了 線程Thread-3運(yùn)行了 ThreadNameThread-0獲得鎖 線程Thread-1運(yùn)行了 ThreadNameThread-1獲得鎖 ThreadNameThread-2獲得鎖 ThreadNameThread-4獲得鎖 ThreadNameThread-3獲得鎖
非公平鎖獲得鎖的順序和線程運(yùn)行的順序不同
getHoldCount()
獲取當(dāng)前線程調(diào)用lock()的次數(shù),一般debug使用。
看個(gè)例子
public class ThreadDomain40 {
private ReentrantLock lock = new ReentrantLock();
public void testMethod1()
{
try
{
lock.lock();
System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
testMethod2();
}
finally
{
lock.unlock();
}
}
public void testMethod2()
{
try
{
lock.lock();
System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
}
finally
{
lock.unlock();
}
}
public static void main(String[] args)
{
ThreadDomain40 td = new ThreadDomain40();
td.testMethod1();
}
}
輸出結(jié)果如下
testMethod1 getHoldCount = 1 testMethod2 getHoldCount = 2
可以看到,testMethod1()被調(diào)用了一次,testMethod2()被調(diào)用了兩次,ReentrantLock和synchronized一樣,鎖都是可重入的。
getQueueLength()和isFair()
getQueueLength()獲取等待的線程數(shù)量,isFair()判斷是否是公平鎖。
ThreadDomain41類和main方法,Thread.sleep(2000)使第一個(gè)線程之后的線程都來不及啟動(dòng),Thread.sleep(Integer.MAX_VALUE)使線程無法unlock()。
public class ThreadDomain41 {
public ReentrantLock lock = new ReentrantLock();
public void testMethod()
{
try
{
lock.lock();
System.out.println("ThreadName = " + Thread.currentThread().getName() + "進(jìn)入方法!");
System.out.println("是否公平鎖?" + lock.isFair());
Thread.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException
{
final ThreadDomain41 td = new ThreadDomain41();
Runnable runnable = new Runnable()
{
public void run()
{
td.testMethod();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < 10; i++)
threads[i].start();
Thread.sleep(2000);
System.out.println("有" + td.lock.getQueueLength() + "個(gè)線程正在等待!");
}
}
輸出結(jié)果如下
ThreadName = Thread-1進(jìn)入方法! 是否公平鎖?false 有9個(gè)線程正在等待!
ReentrantLock默認(rèn)是非公平鎖,只有一個(gè)線程lock(),9個(gè)線程在等待。
hasQueuedThread()和hasQueuedThreads()
hasQueuedThread(Thread thread)查詢指定線程是否在等待鎖,hasQueuedThreads()查詢是否有線程在等待鎖。
看個(gè)例子
ThreadDomain41類和main方法,和上面例子類似,Thread.sleep(Integer.MAX_VALUE); 讓線程不釋放鎖,Thread.sleep(2000);讓第一個(gè)線程之后的線程都無法啟動(dòng)。
public class ThreadDomain42 extends ReentrantLock {
public void waitMethod()
{
try
{
lock();
Thread.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
unlock();
}
}
public static void main(String[] args) throws InterruptedException
{
final ThreadDomain42 td = new ThreadDomain42();
Runnable runnable = new Runnable()
{
public void run()
{
td.waitMethod();
}
};
Thread t0 = new Thread(runnable);
t0.start();
Thread.sleep(500);
Thread t1 = new Thread(runnable);
t1.start();
Thread.sleep(500);
Thread t2 = new Thread(runnable);
t2.start();
Thread.sleep(500);
System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
}
}
輸出結(jié)果如下
t0 is waiting?false t1 is waiting?true t2 is waiting?true Is any thread waiting?true
t0線程獲得了鎖,t0沒有釋放鎖,導(dǎo)致t1,t2等待鎖。
isHeldByCurrentThread()和isLocked()
isHeldByCurrentThread()判斷鎖是否由當(dāng)前線程持有,isLocked()判斷鎖是否由任意線程持有。
請看示例
ThreadDomain43類和main方法
public class ThreadDomain43 extends ReentrantLock {
public void testMethod()
{
try
{
lock();
System.out.println(Thread.currentThread().getName() + "線程持有了鎖!");
System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" +
isHeldByCurrentThread());
System.out.println("是否任意線程持有了鎖?" + isLocked());
} finally
{
unlock();
}
}
public void testHoldLock()
{
System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" +
isHeldByCurrentThread());
System.out.println("是否任意線程持有了鎖?" + isLocked());
}
public static void main(String[] args)
{
final ThreadDomain43 td = new ThreadDomain43();
Runnable runnable0 = new Runnable()
{
public void run()
{
td.testMethod();
}
};
Runnable runnable1 = new Runnable()
{
public void run()
{
td.testHoldLock();
}
};
Thread t0 = new Thread(runnable0);
Thread t1 = new Thread(runnable1);
t0.start();
t1.start();
}
}
輸出結(jié)果如下
Thread-0線程持有了鎖! Thread-1線程是否持有鎖?false Thread-0線程是否持有鎖?true 是否任意線程持有了鎖?true 是否任意線程持有了鎖?true
Thread-0線程testMethod方法持有鎖,Thread-1線程testHoldLock方法沒有l(wèi)ock操作,所以不持有鎖。
tryLock()和tryLock(long timeout, TimeUnit unit)
tryLock()有加鎖的功能,獲得了鎖且鎖沒有被另外一個(gè)線程持有,此時(shí)返回true,否則返回false,可以有效避免死鎖。tryLock(long timeout, TimeUnit unit)表示在給定的時(shí)間內(nèi)獲得了鎖,鎖沒有被其他線程持有,且不處于中斷狀態(tài)。返回true,否則返回false;
看個(gè)例子
public class MyThread39 {
public static void main(String[] args) {
System.out.println("開始");
final Lock lock = new ReentrantLock();
new Thread() {
@Override
public void run() {
String tName = Thread.currentThread().getName();
if (lock.tryLock()) {
System.out.println(tName + "獲取到鎖!");
} else {
System.out.println(tName + "獲取不到鎖!");
return;
}
try {
for (int i = 0; i < 5; i++) {
System.out.println(tName + ":" + i);
}
Thread.sleep(5000);
} catch (Exception e) {
System.out.println(tName + "出錯(cuò)了!");
} finally {
System.out.println(tName + "釋放鎖!");
lock.unlock();
}
}
}.start();
new Thread() {
@Override
public void run() {
String tName = Thread.currentThread().getName();
try {
if (lock.tryLock(1,TimeUnit.SECONDS)) {
System.out.println(tName + "獲取到鎖!");
} else {
System.out.println(tName + "獲取不到鎖!");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
for (int i = 0; i < 5; i++) {
System.out.println(tName + ":" + i);
}
} catch (Exception e) {
System.out.println(tName + "出錯(cuò)");
} finally {
System.out.println(tName + "釋放鎖!");
lock.unlock();
}
}
}.start();
System.out.println("結(jié)束");
}
}
輸出結(jié)果如下
開始 Thread-0獲取到鎖! Thread-0:0 Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 結(jié)束 Thread-1獲取不到鎖! Thread-0釋放鎖!
Thread-0先獲得了鎖,且sleep了5秒,導(dǎo)致Thread-1獲取不到鎖,我們給Thread-1的tryLock設(shè)置1秒,一秒內(nèi)獲取不到鎖就會(huì)返回false。
如果Thread.sleep(0),那么Thread-0和Thread-1都可以獲得鎖,園友可以自己試下。
synchronized和ReentrantLock的比較
1.synchronized關(guān)鍵字是語法層面的實(shí)現(xiàn),ReentrantLock要手動(dòng)lock()和unlock();
2.synchronized是不公平鎖,ReentrantLock可以指定是公平鎖還是非公平鎖;
3.synchronized等待/喚醒機(jī)制是隨機(jī)的,ReentrantLock借助Condition的等待/喚醒機(jī)制可以自行選擇等待/喚醒;
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot config 攔截器使用方法實(shí)例詳解
本文介紹Spring-Boot中使用攔截器的相關(guān)知識,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05
使用JWT創(chuàng)建解析令牌及RSA非對稱加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下2023-11-11
Netty的Handler鏈調(diào)用機(jī)制及如何組織詳解
這篇文章主要為大家介紹了Netty的Handler鏈調(diào)用機(jī)制及如何組織示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
MyBatis Plus 將查詢結(jié)果封裝到指定實(shí)體的方法步驟
這篇文章主要介紹了MyBatis Plus 將查詢結(jié)果封裝到指定實(shí)體的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java 中文字符按Unicode排序的實(shí)現(xiàn)方法
這篇文章主要介紹了Java 中文字符按Unicode排序的實(shí)現(xiàn)方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10

