Java并發(fā)編程顯式鎖

顯式鎖?

有了 synchronized 為什么還要 Lock? Java 程序是靠 synchronized 關(guān)鍵字實(shí)現(xiàn)鎖功能的,使用 synchronized 關(guān)鍵字 將會(huì)隱式地獲取鎖,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。

Synchronized 關(guān)鍵字結(jié)合對(duì)象的監(jiān)視器,JVM 為我們提供了一種『內(nèi)置鎖』的語義,這種鎖很簡便,不需要我們關(guān)心加鎖和釋放鎖的過程,我們只需要告訴虛擬機(jī)哪些代碼塊需要加鎖即可,其他的細(xì)節(jié)會(huì)由編譯器和虛擬機(jī)自己實(shí)現(xiàn)。

可以將我們的『內(nèi)置鎖』理解為是 JVM 的一種內(nèi)置特性,它不支持某些高級(jí)功能的定制,比如說,我想要這個(gè)鎖支持公平競爭,我想要根據(jù)不同的條件將線程阻塞在不同的隊(duì)列上,我想要支持定時(shí)競爭鎖,超時(shí)返回,我還想讓被阻塞的線程能夠響應(yīng)中斷請(qǐng)求等。

這些特殊的需求是『內(nèi)置鎖』滿足不了的,所以在 JDK 層面又引入了『顯式鎖』的概念,不再由 JVM 來負(fù)責(zé)加鎖和釋放鎖,這兩個(gè)動(dòng)作釋放給我們程序來做,程序?qū)用骐y免復(fù)雜了些,但鎖靈活性提高了,可以支持更多定制功能,但要求你對(duì)鎖具有更深層次的理解。

Lock的標(biāo)準(zhǔn)用法

在 finally 塊中釋放鎖,目的是保證在獲取到鎖之后,最終能夠被釋放。 不要將獲取鎖的過程寫在 try 塊中,因?yàn)槿绻讷@取鎖(自定義鎖的實(shí)現(xiàn)) 時(shí)發(fā)生了異常,異常拋出的同時(shí),也會(huì)導(dǎo)致鎖無故釋放。

Lock的常用API

注:

無特殊需求(線程取鎖等待中斷),推薦使用synchronized關(guān)鍵字,從資源消耗上來講,Lock是一個(gè)類,需要消耗內(nèi)存,而synchronized關(guān)鍵字是程序的特寫,無需new出一個(gè)對(duì)象,資源相對(duì)消耗較小。

ReentrantLock? ?可重入鎖 (Lock的實(shí)現(xiàn)類)

鎖的可重入

簡單地講就是:“同一個(gè)線程對(duì)于已經(jīng)獲得到的鎖,可以多次繼續(xù)申請(qǐng)到該 鎖的使用權(quán)”。而 synchronized 關(guān)鍵字隱式的支持重進(jìn)入,比如一個(gè) synchronized 修飾的遞歸方法,在方法執(zhí)行時(shí),執(zhí)行線程在獲取了鎖之后仍能連續(xù)多次地獲得 該鎖。ReentrantLock 在調(diào)用 lock()方法時(shí),已經(jīng)獲取到鎖的線程,能夠再次調(diào)用 lock()方法獲取鎖而不被阻塞

公平和非公平鎖

如果在時(shí)間上,先對(duì)鎖進(jìn)行獲取的請(qǐng)求一定先被滿足,那么這個(gè)鎖是公平的, 反之,是不公平的。公平的獲取鎖,也就是等待時(shí)間最長的線程最優(yōu)先獲取鎖, 也可以說鎖獲取是順序的。 ReentrantLock 提供了一個(gè)構(gòu)造函數(shù),能夠控制鎖是 否是公平的。事實(shí)上,公平的鎖機(jī)制往往沒有非公平的效率高。 在激烈競爭的情況下,非公平鎖的性能高于公平鎖的性能的一個(gè)原因是:在恢 復(fù)一個(gè)被掛起的線程與該線程真正開始運(yùn)行之間存在著嚴(yán)重的延遲。假設(shè)線程 A 持有一個(gè)鎖,并且線程 B 請(qǐng)求這個(gè)鎖。由于這個(gè)鎖已被線程 A 持有,因此 B 將被掛 起。當(dāng) A 釋放鎖時(shí),B 將被喚醒,因此會(huì)再次嘗試獲取鎖。與此同時(shí),如果 C 也請(qǐng)求 這個(gè)鎖,那么 C 很可能會(huì)在 B 被完全喚醒之前獲得、使用以及釋放這個(gè)鎖。這樣 的情況是一種“雙贏”的局面:B 獲得鎖的時(shí)刻并沒有推遲,C 更早地獲得了鎖,并 且吞吐量也獲得了提高。

注 :ReentrantLock 默認(rèn)是非公平鎖,ReentrantLock 通過構(gòu)造方法可設(shè)置公平鎖,synchronized是為非公平鎖,非公平鎖的性能要比公平鎖的高,ReentrantLock 以及synchronized也叫獨(dú)占鎖

ReentrantLock 代碼演示

/**

*類說明:使用Lock的范例

*/

public class LockCase {

private Locklock =new ReentrantLock();

private int age =100000;//初始100000

private static class TestThreadextends Thread{

private LockCaselockCase;

public TestThread(LockCase lockCase, String name) {

super(name);

this.lockCase = lockCase;

}

@Override

public void run() {

for(int i=0;i<100000;i++) {//遞增100000

? ? ? ? ? ? lockCase.test();

}

System.out.println(Thread.currentThread().getName()

+" age =? "+lockCase.getAge());

}

}

public void test() {

lock.lock();

try{

age++;

}finally {

lock.unlock();

}

}

public void test2() {

lock.lock();

try {

age--;

}finally {

lock.unlock();

}

}

public int getAge() {

return age;

}

public static void main(String[] args)throws InterruptedException {

LockCase lockCase =new LockCase();

Thread endThread =new TestThread(lockCase,"endThread");

endThread.start();

for(int i=0;i<100000;i++) {//遞減100000

? ? ? ? lockCase.test2();

}

System.out.println(Thread.currentThread().getName()

+" age =? "+lockCase.getAge());

}

}

讀寫鎖ReentrantReadWriteLock

之前提到鎖(如 Mutex 和 ReentrantLock)基本都是排他鎖,這些鎖在同一 時(shí)刻只允許一個(gè)線程進(jìn)行訪問,而讀寫鎖在同一時(shí)刻可以允許多個(gè)讀線程訪問, 但是在寫線程訪問時(shí),所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護(hù)了一對(duì) 鎖,一個(gè)讀鎖和一個(gè)寫鎖,通過分離讀鎖和寫鎖,使得并發(fā)性相比一般的排他鎖 有了很大提升。?

除了保證寫操作對(duì)讀操作的可見性以及并發(fā)性的提升之外,讀寫鎖能夠簡化 讀寫交互場景的編程方式。假設(shè)在程序中定義一個(gè)共享的用作緩存數(shù)據(jù)結(jié)構(gòu),它 大部分時(shí)間提供讀服務(wù)(例如查詢和搜索),而寫操作占有的時(shí)間很少,但是寫 操作完成之后的更新需要對(duì)后續(xù)的讀服務(wù)可見。?

在沒有讀寫鎖支持的(Java5 之前)時(shí)候,如果需要完成上述工作就要使用 Java 的等待通知機(jī)制,就是當(dāng)寫操作開始時(shí),所有晚于寫操作的讀操作均會(huì)進(jìn)入 等待狀態(tài),只有寫操作完成并進(jìn)行通知之后,所有等待的讀操作才能繼續(xù)執(zhí)行(寫 操作之間依靠 synchronized 關(guān)鍵進(jìn)行同步),這樣做的目的是使讀操作能讀取到 正確的數(shù)據(jù),不會(huì)出現(xiàn)臟讀。改用讀寫鎖實(shí)現(xiàn)上述功能,只需要在讀操作時(shí)獲取 讀鎖,寫操作時(shí)獲取寫鎖即可。當(dāng)寫鎖被獲取到時(shí),后續(xù)(非當(dāng)前寫操作線程) 的讀寫操作都會(huì)被阻塞,寫鎖釋放之后,所有操作繼續(xù)執(zhí)行,編程方式相對(duì)于使 用等待通知機(jī)制的實(shí)現(xiàn)方式而言,變得簡單明了。?

一般情況下,讀寫鎖的性能都會(huì)比排它鎖好,因?yàn)榇蠖鄶?shù)場景讀是多于寫的。 在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量?

ReentrantReadWriteLock 其實(shí)實(shí)現(xiàn)的是 ReadWriteLock 接口

廢話不多說上代碼

實(shí)現(xiàn)讀寫鎖的邏輯

/**

* 類說明:? 讀寫鎖

*/

public class UseRwLockimplements GoodsService{

private GoodsInfogoodsInfo;

private final ReadWriteLocklock =new ReentrantReadWriteLock();

private final LockgetLock =lock.readLock();//讀鎖

private final LocksetLock =lock.writeLock();//寫鎖

public UseRwLock(GoodsInfo goodsInfo) {

this.goodsInfo = goodsInfo;

}

@Override

? ? public GoodsInfo getNum() {

//獲取讀鎖

? ? ? ? getLock.lock();

try{

SleepTools.ms(5);

//TODO 我們平時(shí)的查詢接口

? ? ? ? ? ? return this.goodsInfo;

}finally {

getLock.unlock();

}

}

@Override

? ? public void setNum(int number) {

//獲取寫鎖

? ? ? ? setLock.lock();

try{

SleepTools.ms(5);

//TODO 我們平時(shí)的添加接口

? ? ? ? ? ? goodsInfo.changeNumber(number);

}finally {

setLock.unlock();

}

}

}

調(diào)用

/**

*類說明:對(duì)商品進(jìn)行業(yè)務(wù)的應(yīng)用

*/

public class BusiApp {

static final int readWriteRatio =10;//讀寫線程的比例

? ? static final int minthreadCount =3;//最少線程數(shù)

? ? //讀操作

private static class GetThreadimplements Runnable{

private GoodsServicegoodsService;

public GetThread(GoodsService goodsService) {

this.goodsService = goodsService;

}

@Override

public void run() {

long start = System.currentTimeMillis();

for(int i=0;i<100;i++){//操作100次

? ? ? ? ? ? ? ? goodsService.getNum();

}

System.out.println(Thread.currentThread().getName()+"讀取商品數(shù)據(jù)耗時(shí):"

? ? ? ? ? ? +(System.currentTimeMillis()-start)+"ms");

}

}

//寫操做

private static class SetThreadimplements Runnable{

private GoodsServicegoodsService;

public SetThread(GoodsService goodsService) {

this.goodsService = goodsService;

}

@Override

public void run() {

long start = System.currentTimeMillis();

Random r =new Random();

for(int i=0;i<10;i++){//操作10次

? ? ? ? ? ? ? SleepTools.ms(50);

goodsService.setNum(r.nextInt(10));

}

System.out.println(Thread.currentThread().getName()

+"寫商品數(shù)據(jù)耗時(shí):"+(System.currentTimeMillis()-start)+"ms---------");

}

}

public static void main(String[] args)throws InterruptedException {

GoodsInfo goodsInfo =new GoodsInfo("Cup",100000,10000);

GoodsService goodsService =new UseRwLock(goodsInfo);

for(int i =0;i

Thread setT =new Thread(new SetThread(goodsService));

for(int j=0;j

Thread getT =new Thread(new GetThread(goodsService));

getT.start();

}

SleepTools.ms(100);

setT.start();

}

}

}

Condition接口

Condition常 用 方 法

Condition使 用 范 式

Condition使 用

與 等待通知的范式wait()、notify()相似

/**

*類說明:Condition等待通知演示

*/

public class ExpressCond {

public final static StringCITY ="ShangHai";

private int km;/*快遞運(yùn)輸里程數(shù)*/

private Stringsite;/*快遞到達(dá)地點(diǎn)*/

private LockkmLock =new ReentrantLock();

private LocksiteLock =new ReentrantLock();

private ConditionkmCond =kmLock.newCondition();

private ConditionsiteCond =siteLock.newCondition();

public ExpressCond() {

}

public ExpressCond(int km, String site) {

this.km = km;

this.site = site;

}

/* 變化公里數(shù),然后通知處于wait狀態(tài)并需要處理公里數(shù)的線程進(jìn)行業(yè)務(wù)處理*/

public void changeKm(){

kmLock.lock();

try{

this.km =101;

kmCond.signal();

//kmCond.signalAll();

? ? ? ? }finally {

kmLock.unlock();

}

}

/* 變化地點(diǎn),然后通知處于wait狀態(tài)并需要處理地點(diǎn)的線程進(jìn)行業(yè)務(wù)處理*/

public? void changeSite(){

siteLock.lock();

try {

this.site ="BeiJing";

siteCond.signal();//通知其他在鎖上等待的線程

? ? ? }finally {

siteLock.unlock();

}

}

/*當(dāng)快遞的里程數(shù)大于100時(shí)更新數(shù)據(jù)庫*/

public void waitKm(){

kmLock.lock();

try{

while(this.km<100){

try {

kmCond.await();

System.out.println("Check Site thread["

? ? ? ? ? ? ? ? ? ? ? ? ? ? +Thread.currentThread().getId()

+"] is be notified");

}catch (InterruptedException e) {

e.printStackTrace();

}

}

}finally {

kmLock.unlock();

}

System.out.println("the Km is "+this.km+",I will change db");

}

/*當(dāng)快遞到達(dá)目的地時(shí)通知用戶*/

public void waitSite(){

siteLock.lock();

try {

while(this.site.equals(CITY)) {

try {

siteCond.await();//當(dāng)前線程進(jìn)行等待

? ? ? ? ? ? ? ? System.out.println("check Site thread["+Thread.currentThread().getName()

+"] is be notify");

}catch (InterruptedException e) {

// TODO Auto-generated catch block

? ? ? ? ? ? ? ? e.printStackTrace();

}

}

}finally {

siteLock.unlock();

}

System.out.println("the site is "+this.site+",I will call user");

}

}

/**

*類說明:單鎖的實(shí)現(xiàn) 等待通知

*/

public class ExpressCondOneLock {

public final static StringCITY ="ShangHai";

private int km;/*快遞運(yùn)輸里程數(shù)*/

private Stringsite;/*快遞到達(dá)地點(diǎn)*/

private Locklock =new ReentrantLock();

private ConditionkmCond =lock.newCondition();

private ConditionsiteCond =lock.newCondition();

public ExpressCondOneLock() {

}

public ExpressCondOneLock(int km, String site) {

this.km = km;

this.site = site;

}

/* 變化公里數(shù),然后通知處于wait狀態(tài)并需要處理公里數(shù)的線程進(jìn)行業(yè)務(wù)處理*/

public void changeKm(){

lock.lock();

try {

this.km =101;

kmCond.signal();//通知其他在鎖上等待的線程

? ? ? }finally {

lock.unlock();

}

}

/* 變化地點(diǎn),然后通知處于wait狀態(tài)并需要處理地點(diǎn)的線程進(jìn)行業(yè)務(wù)處理*/

public? void changeSite(){

lock.lock();

try {

this.site ="BeiJing";

siteCond.signal();//通知其他在鎖上等待的線程

? ? ? }finally {

lock.unlock();

}

}

/*當(dāng)快遞的里程數(shù)大于100時(shí)更新數(shù)據(jù)庫*/

public void waitKm(){

lock.lock();

try {

while(this.km<100) {

try {

kmCond.await();//當(dāng)前線程進(jìn)行等待

? ? ? ? ? ? ? ? System.out.println("check km thread["+Thread.currentThread().getName()

+"] is be notify");

}catch (InterruptedException e) {

// TODO Auto-generated catch block

? ? ? ? ? ? ? ? e.printStackTrace();

}

}

}finally {

lock.unlock();

}

System.out.println("the Km is "+this.km+",I will change db");

}

/*當(dāng)快遞到達(dá)目的地時(shí)通知用戶*/

? ? public void waitSite(){

lock.lock();

try {

while(this.site.equals(CITY)) {

try {

siteCond.await();//當(dāng)前線程進(jìn)行等待

? ? ? ? ? ? ? ? System.out.println("check Site thread["+Thread.currentThread().getName()

+"] is be notify");

}catch (InterruptedException e) {

// TODO Auto-generated catch block

? ? ? ? ? ? ? ? e.printStackTrace();

}

}

}finally {

lock.unlock();

}

System.out.println("the site is "+this.site+",I will call user");

}

}

最后為測試類

/**

* 類說明:測試Lock和Condition實(shí)現(xiàn)等待通知

*/

public class TestCond {

private static ExpressCondexpress =new ExpressCond(0, ExpressCond.CITY);

/*檢查里程數(shù)變化的線程,不滿足條件,線程一直等待*/

private static class CheckKmextends Thread {

@Override

public void run() {

express.waitKm();

}

}

/*檢查地點(diǎn)變化的線程,不滿足條件,線程一直等待*/

private static class CheckSiteextends Thread {

@Override

public void run() {

express.waitSite();

}

}

public static void main(String[] args)throws InterruptedException {

for (int i =0; i <3; i++) {

new CheckSite().start();

}

for (int i =0; i <3; i++) {

new CheckKm().start();

}

Thread.sleep(1000);

express.changeKm();//快遞里程變化

? ? }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容