ReentrantLock
ReentrantLock,一個可重入的互斥鎖,它具有與使用synchronized***和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。
ReentrantLock基本用法
先來看一下ReentrantLock的基本用法:
publicclassThreadDomain38{privateLocklock=newReentrantLock();publicvoidtestMethod(){try{lock.lock();for(inti=0;i<2;i++){System.out.println("ThreadName="+Thread.currentThread().getName()+",i="+i);}}finally{lock.unlock();}}}publicclassMyThread38extendsThread{privateThreadDomain38td;publicMyThread38(ThreadDomain38td){this.td=td;}publicvoidrun(){td.testMethod();}}publicstaticvoidmain(String[]args){ThreadDomain38td=newThreadDomain38();MyThread38mt0=newMyThread38(td);MyThread38mt1=newMyThread38(td);MyThread38mt2=newMyThread38(td);mt0.start();mt1.start();mt2.start();}看一下運行結果:
ThreadName=Thread-1,i=0ThreadName=Thread-1,i=1ThreadName=Thread-0,i=0ThreadName=Thread-0,i=1ThreadName=Thread-2,i=0ThreadName=Thread-2,i=1沒有任何的交替,數據都是分組打印的,說明了一個線程打印完畢之后下一個線程才可以獲得鎖去打印數據,這也證明了ReentrantLock具有加鎖的功能
ReentrantLock持有的是對象監視器
前面已經證明了ReentrantLock具有加鎖功能,但我們還不知道ReentrantLock持有的是什么鎖,因此寫個例子看一下:
publicclassThreadDomain39{privateLocklock=newReentrantLock();publicvoidmethodA(){try{lock.lock();System.out.println("MethodAbeginThreadName="+Thread.currentThread().getName());Thread.sleep(5000);System.out.println("MethodAendThreadName="+Thread.currentThread().getName());}catch(InterruptedExceptione){e.printStackTrace();}finally{lock.unlock();}}publicvoidmethodB(){lock.lock();System.out.println("MethodBbeginThreadName="+Thread.currentThread().getName());System.out.println("MethodBbeginThreadName="+Thread.currentThread().getName());lock.unlock();}}寫兩個線程分別調用methodA()和methodB()***:
publicclassMyThread39_0extendsThread{privateThreadDomain39td;publicMyThread39_0(ThreadDomain39td){this.td=td;}publicvoidrun(){td.methodA();}}publicclassMyThread39_1extendsThread{privateThreadDomain39td;publicMyThread39_1(ThreadDomain39td){this.td=td;}publicvoidrun(){td.methodB();}}寫一個main函數啟動這兩個線程:
publicstaticvoidmain(String[]args){ThreadDomain39td=newThreadDomain39();MyThread39_0mt0=newMyThread39_0(td);MyThread39_1mt1=newMyThread39_1(td);mt0.start();mt1.start();}看一下運行結果:
MethodBbeginThreadName=Thread-1MethodBbeginThreadName=Thread-1MethodAbeginThreadName=Thread-0MethodAendThreadName=Thread-0看不見時間,不過第四確實是格了5秒左右才打印出來的。從結果來看,已經證明了ReentrantLock持有的是對象監視器,可以寫一段代碼進一步證明這一結論,即去掉methodB()內部和鎖相關的代碼,只留下兩句打印語句:
MethodAbeginThreadName=Thread-0MethodBbeginThreadName=Thread-1MethodBbeginThreadName=Thread-1MethodAendThreadName=Thread-0看到交替打印了,進一步證明了ReentrantLock持有的是"對象監視器"的結論。
不過注意一點,ReentrantLock雖然持有對象監視器,但是和synchronized持有的對象監視器不是一個意思,雖然我也不清楚兩個持有的對象監視器有什么區別,不過把methodB()***用synchronized修飾,methodA()不變,兩個***還是異步運行的,所以就記一個結論吧----ReentrantLock和synchronized持有的對象監視器不同。
另外,千萬別忘了,ReentrantLock持有的鎖是需要手動去unlock()的
Condition
synchronized與wait()和nitofy()/notifyAll()***相結合可以實現等待/通知模型,ReentrantLock同樣可以,但是需要借助Condition,且Condition有更好的靈活性,具體體現在:
1、一個Lock里面可以創建多個Condition實例,實現多路通知
2、notify()***進行通知時,被通知的線程時Java虛擬機隨機選擇的,但是ReentrantLock結合Condition可以實現有選擇性地通知,這是非常重要的
看一下利用Condition實現等待/通知模型的最簡單用法,下面的代碼注意一下,await()和signal()之前,必須要先lock()獲得鎖,使用完畢在finally中unlock()釋放鎖,這和wait()/notify()/notifyAll()使用前必須先獲得對象鎖是一樣的:
publicclassThreadDomain40{privateLocklock=newReentrantLock();privateConditioncondition=lock.newCondition();publicvoidawait(){try{lock.lock();System.out.println("await時間為:"+System.currentTimeMillis());condition.await();System.out.println("await等待結束");}catch(InterruptedExceptione){e.printStackTrace();}finally{lock.unlock();}}publicvoidsignal(){try{lock.lock();System.out.println("signal時間為:"+System.currentTimeMillis());condition.signal();}finally{lock.unlock();}}}publicclassMyThread40extendsThread{privateThreadDomain40td;publicMyThread40(ThreadDomain40td){this.td=td;}publicvoidrun(){td.await();}}publicstaticvoidmain(String[]args)throwsException{ThreadDomain40td=newThreadDomain40();MyThread40mt=newMyThread40(td);mt.start();Thread.sleep(3000);td.signal();}看一下運行結果:
await時間為:1443970329524signal時間為:1443970332524await等待結束差值是3000毫秒也就是3秒,符合代碼預期,成功利用ReentrantLock的Condition實現了等待/通知模型。其實這個例子還證明了一點,Condition的await()***是釋放鎖的,原因也很簡單,要是await()***不釋放鎖,那么signal()***又怎么能調用到Condition的signal()***呢?
注意要是用一個Condition的話,那么多個線程被該Condition給await()后,調用Condition的signalAll()***喚醒的是所有的線程。如果想單獨喚醒部分線程該怎么辦呢?new出多個Condition就可以了,這樣也有助于提升程序運行的效率。使用多個Condition的場景是很常見的,像ArrayBlockingQueue里就有。