在Java中通常實(shí)現(xiàn)鎖有兩種方式,一種是synchronized關(guān)鍵字,另一種是Lock。二者其實(shí)并沒有什么必然聯(lián)系,但是各有各的特點(diǎn),在使用中可以進(jìn)行取舍的使用。首先我們先對比下兩者。
實(shí)現(xiàn):
首先最大的不同:synchronized是基于JVM層面實(shí)現(xiàn)的,而Lock是基于JDK層面實(shí)現(xiàn)的。Lock卻是基于JDK實(shí)現(xiàn)的,我們可以通過閱讀JDK的源碼來理解Lock的實(shí)現(xiàn)。
使用:
對于使用者的直觀體驗(yàn)上Lock是比較復(fù)雜的,需要lock和realse,如果忘記釋放鎖就會產(chǎn)生死鎖的問題,所以,通常需要在finally中進(jìn)行鎖的釋放。但是synchronized的使用十分簡單,只需要對自己的方法或者關(guān)注的同步對象或類使用synchronized關(guān)鍵字即可。但是對于鎖的粒度控制比較粗,同時(shí)對于實(shí)現(xiàn)一些鎖的狀態(tài)的轉(zhuǎn)移比較困難。
Lock的實(shí)現(xiàn)主要有ReentrantLock、ReadLock和WriteLock,簡單分析一下ReentrantLock的實(shí)現(xiàn)和運(yùn)行機(jī)制。
和lock經(jīng)常搭配的Condition 將 Object 監(jiān)視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實(shí)現(xiàn)組合使用,為每個(gè)對象提供多個(gè)等待 set (wait-set)。
其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監(jiān)視器方法的使用。
在Condition中,用await()替換wait(),用signal()替換notify(),用signalAll()替換notifyAll(),傳統(tǒng)線程的通信方式,Condition都可以實(shí)現(xiàn),這里注意,Condition是被綁定到Lock上的,要創(chuàng)建一個(gè)Lock的Condition必須用newCondition()方法。
這樣看來,Condition和傳統(tǒng)的線程通信沒什么區(qū)別,Condition的強(qiáng)大之處在于它可以為多個(gè)線程間建立不同的Condition(最后的例子體現(xiàn)該特性),下面引入常用方法API。
boolean isFair()
----Returns true if this lock has fairness set true.
----是否是公平鎖
boolean isHeldByCurrentThread()
----Queries if this lock is held by the current thread.
----當(dāng)前線程是否擁有這把鎖
boolean isLocked()
----Queries if this lock is held by any thread.
----鎖是否被鎖定
void lock()
----Acquires the lock.
----獲取鎖
void lockInterruptibly()
----Acquires the lock unless the current thread is interrupted.
----立即響應(yīng)中斷請求的lock
Condition newCondition()
----Returns a Condition instance for use with this Lock instance.
----返回一個(gè)condition
boolean tryLock()
----Acquires the lock only if it is not held by another thread at the time of -----invocation.
----嘗試獲取鎖,返回結(jié)果為boolean
boolean tryLock(long timeout, TimeUnit unit)
void unlock()
----Attempts to release this lock.
----釋放鎖
示例:
public class Reen {
ReentrantLock lock = new ReentrantLock();
Condition con1 = lock.newCondition();
Condition con2 = lock.newCondition();
List<String> list = new ArrayList<>();
void pro(){
while(true){
try {
lock.lock();
while(list.size()==10){
con2.signal();
con1.await();
}
list.add("a");
System.out.println(Thread.currentThread().getName()+" add:"+list.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
void cus(){
while(true){
try {
lock.lock();
System.out.println(lock.getWaitQueueLength(con1));
while(list.size()==0){
con1.signal();
con2.await();
}
list.remove(0);
System.out.println(Thread.currentThread().getName()+" rem:"+list.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception {
Reen r = new Reen();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::pro).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
new Thread(r::cus).start();
}
}
最后,補(bǔ)充提一下這兩中機(jī)制的具體區(qū)別。據(jù)我所知,synchronized原始采用的是CPU悲觀鎖機(jī)制,即線程獲得的是獨(dú)占鎖。獨(dú)占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉(zhuǎn)換線程阻塞時(shí)會引起線程上下文切換,當(dāng)有很多線程競爭鎖的時(shí)候,會引起CPU頻繁的上下文切換導(dǎo)致效率很低。
而Lock用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。樂觀鎖實(shí)現(xiàn)的機(jī)制就是CAS操作(Compare and Swap)。我們可以進(jìn)一步研究ReentrantLock的源代碼,會發(fā)現(xiàn)其中比較重要的獲得鎖的一個(gè)方法是compareAndSetState。這里其實(shí)就是調(diào)用的CPU提供的特殊指令。
思考:為什么判斷l(xiāng)ist長度用while而不用if?