synchronized 和 ReentrantLock
上一篇文章 并發(fā)學(xué)習(xí)之 - synchronized 中我們講解了如何使用關(guān)鍵字 synchronized 來實現(xiàn)同步訪問。從 Java 5 之后,JDK 提供了另外一種方式來實現(xiàn)同步訪問,那就是 ReentrantLock。ReentrantLock 增加了一些高級功能,主要是這 3 項:等待可中斷、可實現(xiàn)公平鎖,以及鎖可以綁定多個條件。
在 JDK 6 之前,盡管 synchronized 關(guān)鍵字實現(xiàn)同步很方便,但是這種同步操作很重量級,很大程度上影響程序的執(zhí)行效率,所以對于開發(fā)者來說,使用起來會有點畏懼。但是在 JDK 6 之后,對 synchronized 進行了很大的優(yōu)化, 引入了偏向鎖,適應(yīng)性自旋,輕量級鎖和重量級鎖,鎖粗化等手段,目前 synchronized 和 ReentrantLock 的性能基本能夠持平,所以一般情況下,還是建議使用 synchronized 關(guān)鍵字,會使得程序簡潔和易讀。
JDK 6 之前

JDK 6 之后

但是 ReentrantLock 也有很多優(yōu)點,ReentrantLock 是 Lock 的實現(xiàn)類,是一個互斥的同步器,在多線程高競爭條件下,ReentrantLock 比 synchronized 有更加優(yōu)異的性能表現(xiàn)。
(1) Lock 使用起來比較靈活,但是必須有釋放鎖的配合動作
Lock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啟鎖 Lock 只適用于代碼塊鎖,而 synchronized 可用于修飾方法、代碼塊等。
(2) ReentrantLock的優(yōu)勢體現(xiàn)在:
具備嘗試非阻塞地獲取鎖的特性:當(dāng)前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖的特性:與synchronized不同,獲取到鎖的線程能夠響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放
超時獲取鎖的特性:在指定的時間范圍內(nèi)獲取鎖;如果截止時間到了仍然無法獲取鎖,則返回
(3) 在使用 ReentrantLock 時,要注意:在 finally 中釋放鎖,目的是保證在獲取鎖之后,最終能夠被釋放
不要將獲取鎖的過程寫在 try 塊內(nèi),因為如果在獲取鎖時發(fā)生了異常,異常拋出的同時,也會導(dǎo)致鎖無法被釋放。
(4) ReentrantLock 提供了一個 newCondition 的方法,以便用戶在同一鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒的動作。
2. ReentrantLock 用法
ReentrantLock 是 Lock 接口的實現(xiàn)類,Lock 提供了以下幾個方法:
// 獲取鎖,獲取過程中不可中斷
void lock();
// 獲取鎖,獲取過程中可中斷,中斷后拋出 InterruptedException 異常
void lockInterruptibly() throws InterruptedException;
// 非阻塞獲取鎖,無論是否獲取到均返回,true 獲取成功,false 獲取失敗
boolean tryLock();
// 嘗試獲取鎖,在 time 時間內(nèi)獲取到返回 true,超時則返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖,一般在 finally 塊中
void unlock();
// 創(chuàng)建等待條件
Condition newCondition();
2.1 Lock 鎖使用
ReentrantLock 鎖使用起來也比較簡單,主要根據(jù)場景來進行選擇,有一點需要注意,如果不是使用 tryLock 的方法,需要在 finally 塊中釋放鎖。下面給出幾種獲取鎖的使用示例代碼。
public class ReentrantLockTest {
private static final ReentrantLock reentrantLock = new ReentrantLock();
private static int sCount = 0;
/**
* lock
*/
private static class MyRunnable implements Runnable {
@Override
public void run() {
reentrantLock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
}
Thread.sleep(3500);
} catch (Exception e) {
} finally {
reentrantLock.unlock();
}
}
}
/**
* tryLock
*/
private static class MyRunnable1 implements Runnable {
@Override
public void run() {
boolean isLock = reentrantLock.tryLock();
if (isLock) {
execute();
} else {
System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【失敗】");
}
}
}
/**
* tryLock(long timeout, TimeUnit unit),超時獲取失敗,中間可以被中斷
*/
private static class MyRunnable2 implements Runnable {
@Override
public void run() {
boolean isLock;
try {
// isLock = reentrantLock.tryLock(3, TimeUnit.SECONDS);
isLock = reentrantLock.tryLock(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【被中斷】");
return;
}
if (isLock) {
execute();
} else {
System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【失敗】");
}
}
}
/**
* lockInterruptibly 中間可以被中斷,如果不中斷就一直嘗試獲取鎖
*/
private static class MyRunnable3 implements Runnable {
@Override
public void run() {
try {
reentrantLock.lockInterruptibly();
execute();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【被中斷】");
}
}
}
private static void execute() {
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
}
} catch (Exception e) {
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
sCount = 0;
// lock
// test1();
// tryLock
// test2();
// tryLock(long timeout, TimeUnit unit)
// test3();
// lockInterruptibly
test4();
}
private static void test1() {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
private static void test2() {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable1());
thread1.start();
thread2.start();
}
private static void test3() {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable2());
thread1.start();
thread2.start();
// 中斷測試
thread1.interrupt();
thread2.interrupt();
}
private static void test4() throws InterruptedException {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable3());
thread1.start();
thread2.start();
Thread.sleep(2000);
// 中斷測試
thread1.interrupt();
thread2.interrupt();
}
}
2.2 Condition 使用
對應(yīng)一個 ReentrantLock 鎖,可以有多個條件,這是 synchronized 不具備的靈活性。synchronized 中方法或者塊中,只能使用鎖的 wait() 和 notify()、notifyAll()。而 ReentrantLock 的一個鎖可以有多個條件,ReentrantLock 的一個鎖對應(yīng)一個同步隊列,多個條件對應(yīng)多個等待隊列。下面使用 ReentrantLock 的條件實現(xiàn)生產(chǎn)者 - 消費者模型。
public class ProducerConsumer1 {
private static final ReentrantLock sLock = new ReentrantLock();
private static final Condition sProducerCondition = sLock.newCondition();
private static final Condition sConsumerCondition = sLock.newCondition();
private static final int MAX = 10;
private static int count = 0;
private static class Producer implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
sLock.lock();
try {
if (count == MAX) {
sProducerCondition.await();
}
count++;
System.out.println("生產(chǎn)者 ---- > " + count);
Thread.sleep(500);
sConsumerCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
sLock.unlock();
}
}
}
}
private static class Consumer implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
sLock.lock();
try {
if (count == 0) {
sConsumerCondition.await();
}
count--;
System.out.println("消費者 ---- > " + count);
Thread.sleep(300);
sProducerCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
sLock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread producer = new Thread(new Producer());
Thread consumer = new Thread(new Consumer());
producer.start();
consumer.start();
Thread.sleep(10000);
}
}
2.3 其他說明
ReetrantLock 和 synchronized 都是一種支持重進入的鎖,即該鎖可以支持一個線程對資源重復(fù)加鎖。但是 synchronized 不支持公平鎖,ReetrantLock 支持公平鎖與非公平鎖。公平與非公平指的是在請求先后順序上,先對鎖進行請求的就一定先獲取到鎖,那么這就是公平鎖,反之,如果對于鎖的獲取并沒有時間上的先后順序,如后請求的線程可能先獲取到鎖,這就是非公平鎖,一般而言非,非公平鎖機制的效率往往會勝過公平鎖的機制,但在某些場景下,可能更注重時間先后順序,那么公平鎖自然是很好的選擇。需要注意的是 ReetrantLock 支持對同一線程重加鎖,但是加鎖多少次,就必須解鎖多少次,這樣才可以成功釋放鎖。