ReentrantLock
除了使用synchronized
關鍵字來實現線程之間同步互斥,在JDK1.5中新增了ReentrantLock
類也能達到同樣的效果,並且在擴展功能上也更加強大,例如嗅探鎖定、多路分支通知等功能,使用上比synchronized
更加靈活。
主要的使用方法:
Lock lock = new ReentrantLock();
try {
// 獲取鎖
lock.lock();
doSomething();
} finally {
// 釋放鎖
lock.unlock();
}
這裏將synchronized
的同步代碼塊的獲取鎖和釋放鎖變成手動控制,更加靈活,但是需要注意鎖沒有釋放的問題,一般在try-catch
的語句中可以將unlock
放到finally
語句塊中,確保鎖一定會釋放。
使用Condition類
與關鍵字synchronized
的wait(),notify()/notifyAll()方法類似,ReentrantLock
類也提供了Condition類來實現等待/通知模式,它還可以通過多個Condition實現多路通知功能,有選擇性的選擇一部分等待線程進行喚醒。
而synchronized
就只能通過notify()隨機喚醒一個線程,或者notifyAll()喚醒所有線程,靈活性和效率都不及ReentranLock
。
具體的用法如下:
Lock lock = new ReentrantLock();
// 新增Condition對象
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
// 釋放鎖並等待喚醒,只有conditionA的signal()/signalAll()方法能喚醒
conditionA.await(); // 對應Object對象的wait()方法
// 喚醒等待conditionA條件喚醒的線程
conditionA.signal(); // 喚醒一個,對應Object對象的notify()方法
conditionA.signalAll(); // 全部喚醒,對應Object對象的notifyAll()方法
範例中通過多個Condition對象來實現對線程的等待/通知機制進行隔離,線程是等待和發出喚醒都是基於一個Condition條件,不同條件的Condition之間的喚醒相互不影響。
需要注意的是,await()/signal()/signalAll()
方法都需要在調用了lock()方法獲取鎖之後才能調用,否則會拋出IllegalMonitorStateException
,此條件與Object類的wait()/notify()/notifyAll()
相同,都需要在先獲取鎖的場景下調用,假設調用lock()時候鎖被其他線程佔用,這時候則會阻塞線程,等待鎖的釋放。
多生產者與多消費者的假死問題
看下面的代碼片段,這個代碼片段可能導致程序的假死,導致假死的執行順序如下:
經過一段時間後,消費者b, bb都進入等待,生產者aa也是等待狀態,這時候生產者a被喚醒,生產了一個值,調用signal()隨機喚醒一個線程,這時候可能喚醒了生產者aa,生產者aa喚醒後發現hasValue=true則釋放鎖並進入等待狀態,這時候四個線程都是等待狀態,就出現了假死的情況。
package lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService1 {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean hasValue = false;
public void set() {
try {
lock.lock();
while (hasValue) {
condition.await();
}
System.out.println("打印★");
hasValue = true;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get() {
try {
lock.lock();
while (!hasValue) {
condition.await();
}
System.out.println("打印☆");
hasValue = false;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyService1 service1 = new MyService1();
MyThreadA a = new MyThreadA(service1);
MyThreadA aa = new MyThreadA(service1);
a.start();
aa.start();
MyThreadB b = new MyThreadB(service1);
MyThreadB bb = new MyThreadB(service1);
b.start();
bb.start();
}
}
class MyThreadA extends Thread {
private MyService1 myService1;
MyThreadA(MyService1 myService1) {
this.myService1 = myService1;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService1.set();
}
}
}
class MyThreadB extends Thread {
private MyService1 myService1;
MyThreadB(MyService1 myService1) {
this.myService1 = myService1;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService1.get();
}
}
}
想要解決這個問題,只要將程序中兩個condition.signal();
替換成condition.signalAll();
即可,這樣保證每次某個線程執行完成之後,其他的各個線程都能得到通知,並且檢查自己是否需要執行操作。
這個做法同樣可以通過多個Condition對象來實現這個效果,代碼片段如下:
package lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService1 {
private final Lock lock = new ReentrantLock();
private final Condition emptyCondition = lock.newCondition();
private final Condition fullCondition = lock.newCondition();
private boolean hasValue = false;
public void set() {
try {
lock.lock();
while (hasValue) {
emptyCondition.await();
}
System.out.println("打印★");
hasValue = true;
fullCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get() {
try {
lock.lock();
while (!hasValue) {
fullCondition.await();
}
System.out.println("打印☆");
hasValue = false;
emptyCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyService1 service1 = new MyService1();
MyThreadA a = new MyThreadA(service1);
MyThreadA aa = new MyThreadA(service1);
a.start();
aa.start();
MyThreadB b = new MyThreadB(service1);
MyThreadB bb = new MyThreadB(service1);
b.start();
bb.start();
}
}
class MyThreadA extends Thread {
private MyService1 myService1;
MyThreadA(MyService1 myService1) {
this.myService1 = myService1;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService1.set();
}
}
}
class MyThreadB extends Thread {
private MyService1 myService1;
MyThreadB(MyService1 myService1) {
this.myService1 = myService1;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService1.get();
}
}
}
公平鎖和非公平鎖
公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來分配,即先來先到的FIFO先進先出順序。
非公平鎖:當一個線程釋放鎖之後,其他因爲加鎖而阻塞的線程,會使用搶佔機制,等待中的線程隨機獲取得到鎖;非公平鎖可能導致某些線程一直搶不到鎖的問題。
ReentrantLock
默認的採用非公平鎖,可以從他的構造器中看到:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync(); // 默認構造器是非公平鎖
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync(); // 用戶自定義鎖類型
}
常用的一些方法
ReentrantLock
int getHoldCount()
查詢當前線程調用了lock()方法的次數,相當於synchronized()
方法的重入次數;int getQueueLength()
返回正等待獲取獲取該鎖的線程估計數;int getWaitQueueLength(Condition condition)
返回等待與該鎖相關的給定條件Condition
的線程估計數;- `boolean hasQueuedThread(Thread thread)查詢指定的線程是否正在等待獲取這個鎖;
boolean hasWaiters(Condition condition)
查詢是否有線程正在等待與該鎖相關的Condition條件(即調用了condition的await()方法);isFair()
判斷是不是公平鎖;isHeldByCurrentThread()
查詢當前線程是否持有該鎖;boolean isLocked()
查詢該鎖是否被某個線程持有(不一定是當前線程);lockInterruptibly()
如果當前線程沒有打上中斷標記(thread.interrupt()),則獲取該鎖,否則拋出異常;tryLock()
僅在調用時候鎖未被另一個線程持有的情況下,才獲取該鎖,否則不獲取鎖,直接執行下方代碼;tryLock(long timeout, TimeUnit unit)
如果鎖在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖。
Condition
awaitUninterruptibly()
正常來說,當執行condition.await()
時候,將線程打上中斷標記,則會拋出異常,該方法相當於忽略中斷標記的await()
方法;awaitUntil(long)
延時版本的await()
方法,可以在等待指定毫秒數後自動喚醒,或者在自動喚醒之前被其他線程喚醒。
參考:
jdk8的javadoc文檔
ReentrantReadWriteLock類
類ReentrantLock
具有完成互斥排他的效果,即統一時間只有一個線程在執行ReentrantLock.lock()
方法後面的任務,這樣做效率比較低下。
所以JDK提供了一種讀寫鎖ReentrantReadWriteLock
類,可以加快運行效率,分開讀鎖和寫鎖。
讀寫鎖標識也有兩個鎖,一個是讀操作相關的鎖,也叫做共享鎖;另一個是寫操作相關的鎖,也叫做排他鎖。
用法
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
# 讀鎖加鎖、釋放鎖
lock.readLock().lock();
lock.readLock().unlock();
# 寫鎖加鎖
lock.writeLock().lock();
lock.writeLock().unlock();
互斥策略爲:
- 讀讀共享
- 讀寫互斥
- 寫寫互斥
一個總的原則就是讀的加鎖都是可以多次加鎖,也就是共享的,寫的加鎖是與其他的鎖(讀鎖、寫鎖)都是互斥的,寫鎖需要自己獨佔,寫鎖被某個線程獲取之後,其他線程均不能再對該鎖獲取任何類型的鎖,直到寫鎖釋放。