顯式鎖?
有了 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();//快遞里程變化
? ? }
}