您好,登錄后才能下訂單哦!
本篇文章開(kāi)始將juc中常用的一些類,估計(jì)會(huì)有十來(lái)篇。
synchronized是java內(nèi)置的關(guān)鍵字,它提供了一種獨(dú)占的加鎖方式。synchronized的獲取和釋放鎖由jvm實(shí)現(xiàn),用戶不需要顯示的釋放鎖,非常方便,然而synchronized也有一定的局限性,例如:
JDK1.5之后發(fā)布,加入了Doug Lea實(shí)現(xiàn)的java.util.concurrent包。包內(nèi)提供了Lock類,用來(lái)提供更多擴(kuò)展的加鎖功能。Lock彌補(bǔ)了synchronized的局限,提供了更加細(xì)粒度的加鎖功能。
ReentrantLock是Lock的默認(rèn)實(shí)現(xiàn),在聊ReentranLock之前,我們需要先弄清楚一些概念:
我們使用3個(gè)線程來(lái)對(duì)一個(gè)共享變量++操作,先使用synchronized實(shí)現(xiàn),然后使用ReentrantLock實(shí)現(xiàn)。
synchronized方式:
package com.itsoku.chat06;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo2 {
private static int num = 0;
private static synchronized void add() {
num++;
}
public static class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo2.add();
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
T t2 = new T();
T t3 = new T();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(Demo2.num);
}
}
ReentrantLock方式:
package com.itsoku.chat06;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo3 {
private static int num = 0;
private static ReentrantLock lock = new ReentrantLock();
private static void add() {
lock.lock();
try {
num++;
} finally {
lock.unlock();
}
}
public static class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo3.add();
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
T t2 = new T();
T t3 = new T();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(Demo3.num);
}
}
ReentrantLock的使用過(guò)程:
對(duì)比上面的代碼,與關(guān)鍵字synchronized相比,ReentrantLock鎖有明顯的操作過(guò)程,開(kāi)發(fā)人員必須手動(dòng)的指定何時(shí)加鎖,何時(shí)釋放鎖,正是因?yàn)檫@樣手動(dòng)控制,ReentrantLock對(duì)邏輯控制的靈活度要遠(yuǎn)遠(yuǎn)勝于關(guān)鍵字synchronized,上面代碼需要注意lock.unlock()一定要放在finally中,否則,若程序出現(xiàn)了異常,鎖沒(méi)有釋放,那么其他線程就再也沒(méi)有機(jī)會(huì)獲取這個(gè)鎖了。
來(lái)驗(yàn)證一下ReentrantLock是可重入鎖,實(shí)例代碼:
package com.itsoku.chat06;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo4 {
private static int num = 0;
private static ReentrantLock lock = new ReentrantLock();
private static void add() {
lock.lock();
lock.lock();
try {
num++;
} finally {
lock.unlock();
lock.unlock();
}
}
public static class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo4.add();
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
T t2 = new T();
T t3 = new T();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(Demo4.num);
}
}
上面代碼中add()方法中,當(dāng)一個(gè)線程進(jìn)入的時(shí)候,會(huì)執(zhí)行2次獲取鎖的操作,運(yùn)行程序可以正常結(jié)束,并輸出和期望值一樣的30000,假如ReentrantLock是不可重入的鎖,那么同一個(gè)線程第2次獲取鎖的時(shí)候由于前面的鎖還未釋放而導(dǎo)致死鎖,程序是無(wú)法正常結(jié)束的。ReentrantLock命名也挺好的Re entrant Lock,和其名字一樣,可重入鎖。
代碼中還有幾點(diǎn)需要注意:
在大多數(shù)情況下,鎖的申請(qǐng)都是非公平的,也就是說(shuō),線程1首先請(qǐng)求鎖A,接著線程2也請(qǐng)求了鎖A。那么當(dāng)鎖A可用時(shí),是線程1可獲得鎖還是線程2可獲得鎖呢?這是不一定的,系統(tǒng)只是會(huì)從這個(gè)鎖的等待隊(duì)列中隨機(jī)挑選一個(gè),因此不能保證其公平性。這就好比買票不排隊(duì),大家都圍在售票窗口前,售票員忙的焦頭爛額,也顧及不上誰(shuí)先誰(shuí)后,隨便找個(gè)人出票就完事了,最終導(dǎo)致的結(jié)果是,有些人可能一直買不到票。而公平鎖,則不是這樣,它會(huì)按照到達(dá)的先后順序獲得資源。公平鎖的一大特點(diǎn)是:它不會(huì)產(chǎn)生饑餓現(xiàn)象,只要你排隊(duì),最終還是可以等到資源的;synchronized關(guān)鍵字默認(rèn)是有jvm內(nèi)部實(shí)現(xiàn)控制的,是非公平鎖。而ReentrantLock運(yùn)行開(kāi)發(fā)者自己設(shè)置鎖的公平性。
看一下jdk中ReentrantLock的源碼,2個(gè)構(gòu)造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默認(rèn)構(gòu)造方法創(chuàng)建的是非公平鎖。
第2個(gè)構(gòu)造方法,有個(gè)fair參數(shù),當(dāng)fair為true的時(shí)候創(chuàng)建的是公平鎖,公平鎖看起來(lái)很不錯(cuò),不過(guò)要實(shí)現(xiàn)公平鎖,系統(tǒng)內(nèi)部肯定需要維護(hù)一個(gè)有序隊(duì)列,因此公平鎖的實(shí)現(xiàn)成本比較高,性能相對(duì)于非公平鎖來(lái)說(shuō)相對(duì)低一些。因此,在默認(rèn)情況下,鎖是非公平的,如果沒(méi)有特別要求,則不建議使用公平鎖。
公平鎖和非公平鎖在程序調(diào)度上是很不一樣,來(lái)一個(gè)公平鎖示例看一下:
package com.itsoku.chat06;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo5 {
private static int num = 0;
private static ReentrantLock fairLock = new ReentrantLock(true);
public static class T extends Thread {
public T(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
fairLock.lock();
try {
System.out.println(this.getName() + "獲得鎖!");
} finally {
fairLock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T("t1");
T t2 = new T("t2");
T t3 = new T("t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
運(yùn)行結(jié)果輸出:
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
看一下輸出的結(jié)果,鎖時(shí)按照先后順序獲得的。
修改一下上面代碼,改為非公平鎖試試,如下:
ReentrantLock fairLock = new ReentrantLock(false);
運(yùn)行結(jié)果如下:
t1獲得鎖!
t3獲得鎖!
t3獲得鎖!
t3獲得鎖!
t3獲得鎖!
t1獲得鎖!
t1獲得鎖!
t1獲得鎖!
t1獲得鎖!
t2獲得鎖!
t2獲得鎖!
t2獲得鎖!
t2獲得鎖!
t2獲得鎖!
t3獲得鎖!
可以看到t3可能會(huì)連續(xù)獲得鎖,結(jié)果是比較隨機(jī)的,不公平的。
對(duì)于synchronized關(guān)鍵字,如果一個(gè)線程在等待獲取鎖,最終只有2種結(jié)果:
而ReentrantLock提供了另外一種可能,就是在等的獲取鎖的過(guò)程中(發(fā)起獲取鎖請(qǐng)求到還未獲取到鎖這段時(shí)間內(nèi))是可以被中斷的,也就是說(shuō)在等待鎖的過(guò)程中,程序可以根據(jù)需要取消獲取鎖的請(qǐng)求。有些使用這個(gè)操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小時(shí)朋友還沒(méi)到,突然你接到一個(gè)電話,朋友由于突發(fā)狀況,不能來(lái)了,那么你一定達(dá)到回府。中斷操作正是提供了一套類似的機(jī)制,如果一個(gè)線程正在等待獲取鎖,那么它依然可以收到一個(gè)通知,被告知無(wú)需等待,可以停止工作了。
示例代碼:
package com.itsoku.chat06;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo6 {
private static ReentrantLock lock1 = new ReentrantLock(false);
private static ReentrantLock lock2 = new ReentrantLock(false);
public static class T extends Thread {
int lock;
public T(String name, int lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
try {
if (this.lock == 1) {
lock1.lockInterruptibly();
TimeUnit.SECONDS.sleep(1);
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
TimeUnit.SECONDS.sleep(1);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
System.out.println("中斷標(biāo)志:" + this.isInterrupted());
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T("t1", 1);
T t2 = new T("t2", 2);
t1.start();
t2.start();
}
}
先運(yùn)行一下上面代碼,發(fā)現(xiàn)程序無(wú)法結(jié)束,使用jstack查看線程堆棧信息,發(fā)現(xiàn)2個(gè)線程死鎖了。
Found one Java-level deadlock:
=============================
"t2":
waiting for ownable synchronizer 0x0000000717380c20, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "t1"
"t1":
waiting for ownable synchronizer 0x0000000717380c50, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "t2"
lock1被線程t1占用,lock2倍線程t2占用,線程t1在等待獲取lock2,線程t2在等待獲取lock1,都在相互等待獲取對(duì)方持有的鎖,最終產(chǎn)生了死鎖,如果是在synchronized關(guān)鍵字情況下發(fā)生了死鎖現(xiàn)象,程序是無(wú)法結(jié)束的。
我們隊(duì)上面代碼改造一下,線程t2一直無(wú)法獲取到lock1,那么等待5秒之后,我們中斷獲取鎖的操作。主要修改一下main方法,如下:
T t1 = new T("t1", 1);
T t2 = new T("t2", 2);
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(5);
t2.interrupt();
新增了2行代碼TimeUnit.SECONDS.sleep(5);t2.interrupt();
,程序可以結(jié)束了,運(yùn)行結(jié)果:
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.itsoku.chat06.Demo6$T.run(Demo6.java:31)
中斷標(biāo)志:false
從上面信息中可以看出,代碼的31行觸發(fā)了異常,中斷標(biāo)志輸出:false
t2在31行一直獲取不到lock1的鎖,主線程中等待了5秒之后,t2線程調(diào)用了interrupt()
方法,將線程的中斷標(biāo)志置為true,此時(shí)31行會(huì)觸發(fā)InterruptedException
異常,然后線程t2可以繼續(xù)向下執(zhí)行,釋放了lock2的鎖,然后線程t1可以正常獲取鎖,程序得以繼續(xù)進(jìn)行。線程發(fā)送中斷信號(hào)觸發(fā)InterruptedException異常之后,中斷標(biāo)志將被清空。
關(guān)于獲取鎖的過(guò)程中被中斷,注意幾點(diǎn):
lockInterruptibly()
獲取鎖時(shí),在線程調(diào)用interrupt()方法之后,才會(huì)引發(fā)InterruptedException
異常申請(qǐng)鎖等待限時(shí)是什么意思?一般情況下,獲取鎖的時(shí)間我們是不知道的,synchronized關(guān)鍵字獲取鎖的過(guò)程中,只能等待其他線程把鎖釋放之后才能夠有機(jī)會(huì)獲取到所。所以獲取鎖的時(shí)間有長(zhǎng)有短。如果獲取鎖的時(shí)間能夠設(shè)置超時(shí)時(shí)間,那就非常好了。
ReentrantLock剛好提供了這樣功能,給我們提供了獲取鎖限時(shí)等待的方法tryLock()
,可以選擇傳入時(shí)間參數(shù),表示等待指定的時(shí)間,無(wú)參則表示立即返回鎖申請(qǐng)的結(jié)果:true表示獲取鎖成功,false表示獲取鎖失敗。
看一下源碼中tryLock方法:
public boolean tryLock()
返回boolean類型的值,此方法會(huì)立即返回,結(jié)果表示獲取鎖是否成功,示例:
package com.itsoku.chat06;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo8 {
private static ReentrantLock lock1 = new ReentrantLock(false);
public static class T extends Thread {
public T(String name) {
super(name);
}
@Override
public void run() {
try {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "開(kāi)始獲取鎖!");
//獲取鎖超時(shí)時(shí)間設(shè)置為3秒,3秒內(nèi)是否能否獲取鎖都會(huì)返回
if (lock1.tryLock()) {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "獲取到了鎖!");
//獲取到鎖之后,休眠5秒
TimeUnit.SECONDS.sleep(5);
} else {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "未能獲取到鎖!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T("t1");
T t2 = new T("t2");
t1.start();
t2.start();
}
}
代碼中獲取鎖成功之后,休眠5秒,會(huì)導(dǎo)致另外一個(gè)線程獲取鎖失敗,運(yùn)行代碼,輸出:
1563356291081:t2開(kāi)始獲取鎖!
1563356291081:t2獲取到了鎖!
1563356291081:t1開(kāi)始獲取鎖!
1563356291081:t1未能獲取到鎖!
可以看到t2獲取成功,t1獲取失敗了,tryLock()是立即響應(yīng)的,中間不會(huì)有阻塞。
可以明確設(shè)置獲取鎖的超時(shí)時(shí)間,該方法簽名:
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
該方法在指定的時(shí)間內(nèi)不管是否可以獲取鎖,都會(huì)返回結(jié)果,返回true,表示獲取鎖成功,返回false表示獲取失敗。此方法由2個(gè)參數(shù),第一個(gè)參數(shù)是時(shí)間類型,是一個(gè)枚舉,可以表示時(shí)、分、秒、毫秒等待,使用比較方便,第1個(gè)參數(shù)表示在時(shí)間類型上的時(shí)間長(zhǎng)短。此方法在執(zhí)行的過(guò)程中,如果調(diào)用了線程的中斷interrupt()方法,會(huì)觸發(fā)InterruptedException異常。
示例:
package com.itsoku.chat06;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號(hào):javacode2018,獲取年薪50萬(wàn)課程
*/
public class Demo7 {
private static ReentrantLock lock1 = new ReentrantLock(false);
public static class T extends Thread {
public T(String name) {
super(name);
}
@Override
public void run() {
try {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "開(kāi)始獲取鎖!");
//獲取鎖超時(shí)時(shí)間設(shè)置為3秒,3秒內(nèi)是否能否獲取鎖都會(huì)返回
if (lock1.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "獲取到了鎖!");
//獲取到鎖之后,休眠5秒
TimeUnit.SECONDS.sleep(5);
} else {
System.out.println(System.currentTimeMillis() + ":" + this.getName() + "未能獲取到鎖!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T("t1");
T t2 = new T("t2");
t1.start();
t2.start();
}
}
程序中調(diào)用了ReentrantLock的實(shí)例方法tryLock(3, TimeUnit.SECONDS)
,表示獲取鎖的超時(shí)時(shí)間是3秒,3秒后不管是否能否獲取鎖,該方法都會(huì)有返回值,獲取到鎖之后,內(nèi)部休眠了5秒,會(huì)導(dǎo)致另外一個(gè)線程獲取鎖失敗。
運(yùn)行程序,輸出:
1563355512901:t2開(kāi)始獲取鎖!
1563355512901:t1開(kāi)始獲取鎖!
1563355512902:t2獲取到了鎖!
1563355515904:t1未能獲取到鎖!
輸出結(jié)果中分析,t2獲取到鎖了,然后休眠了5秒,t1獲取鎖失敗,t1打印了2條信息,時(shí)間相差3秒左右。
關(guān)于tryLock()方法和tryLock(long timeout, TimeUnit unit)方法,說(shuō)明一下:
InterruptedException
異常,這個(gè)從2個(gè)方法的聲明上可以可以看出來(lái)獲取鎖的方法 | 是否立即響應(yīng)(不會(huì)阻塞) | 是否響應(yīng)中斷 |
---|---|---|
lock() | × | × |
lockInterruptibly() | × | √ |
tryLock() | √ | × |
tryLock(long timeout, TimeUnit unit) | × | √ |
InterruptedException
異常InterruptedException
異常說(shuō)一下,看到方法聲明上帶有 throws InterruptedException
,表示該方法可以相應(yīng)線程中斷,調(diào)用線程的interrupt()方法時(shí),這些方法會(huì)觸發(fā)InterruptedException
異常,觸發(fā)InterruptedException時(shí),線程的中斷中斷狀態(tài)會(huì)被清除。所以如果程序由于調(diào)用interrupt()
方法而觸發(fā)InterruptedException
異常,線程的標(biāo)志由默認(rèn)的false變?yōu)閠ure,然后又變?yōu)閒alsejava高并發(fā)系列連載中,總計(jì)估計(jì)會(huì)有四五十篇文章,可以關(guān)注公眾號(hào):javacode2018,獲取最新文章。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。