ReentrantLock類
為了保證任何時刻隻有一個線程能進入臨界區,通常需要給臨界區上鎖,隻有獲得鎖的線程才能進入臨界區。為了達到上鎖的目的,我們通常使用synchronized關鍵字。
在Java SE 5.0之後,java引入了一個ReentrantLock類,也可以實作給代碼塊上鎖和釋放鎖的效果。
lock方法 和unlock方法
- lock() 申請獲得鎖
- 如果獲得鎖,該線程可以繼續往下執行
- 如果該鎖已被其他線程擷取,目前線程停止運作并進入阻塞狀态,等待其他線程釋放鎖
- unlock() 釋放鎖
- ReentrantLock和synchronized鎖一樣是可重入鎖,
- 一個線程可以重複的獲得已經持有的鎖,鎖保持一個持有計數(holdcount) 來跟蹤對 lock 方法的嵌套調用。
- 線程在每一次調用 lock 都要調用 unlock 來釋放鎖。
- 由于這一特性, 被一個可重入鎖保護的代碼可以調用另一個使用相同的鎖的方法。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9 {
//ReentrantLock類實作了Lock接口
private static final Lock lock = new ReentrantLock();
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public void add() {
lock.lock();//申請獲得鎖
try {
//一般為了安全,一般用try把同步代碼塊的内容包裹起來
//保證即使目前線程運作過程中出現異常,也能正常釋放鎖
for (int i = 0; i < 10000; i++) {
count++;
}
} finally {
//一般在finally裡釋放鎖
lock.unlock();
}
}
public static void main(String[] args) {
myThread9 m = new myThread9();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
//建立10個線程,加入threads中
Thread thread = new Thread(() -> {
m.add();
}, "thread-" + i);
threads.add(thread);
}
//逐一啟動線程
threads.forEach((thread -> thread.start()));
//在主線程中利用join方法,等待所有方法完成
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//所有子線程運作結束,列印count值
System.out.println(m.getCount());
}
}

trylock方法(限時等待)
- trylock():嘗試獲得鎖,如果獲得鎖,傳回true.如果沒有獲得鎖,傳回false(目前線程不會進入阻塞狀态)
- trylock(long time,TimeUnit unit):在指定時間時間内嘗試獲得鎖,如果成功擷取鎖,傳回true,如果沒有鎖,傳回false(目前線程不會進入阻塞狀态)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9_1{
//ReentrantLock類實作了Lock接口
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
myThread9_1 m = new myThread9_1();
//啟動第一個線程,執行add方法
new Thread(()->m.add(),"t1").start();
//過1s後啟動第二個線程執行testTryLock,確定第一個線程能先啟動獲得鎖
Thread.sleep(1000);
new Thread(()->m.testTryLock(),"t2").start();
}
public void add(){
lock.lock();
try {
//該線程獲得鎖後需要5s才能運作完畢釋放鎖
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+":目前i的值為"+i);
Thread.sleep(1000);
}}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void testTryLock(){
boolean flag=false;
try {
//可以嘗試不同等待時間對比運作結果
flag = lock.tryLock();//嘗試立即獲得鎖
// flag = lock.tryLock(2,TimeUnit.SECONDS);//等待2s,嘗試獲得鎖
// flag = lock.tryLock(6,TimeUnit.SECONDS);//等待6s,嘗試獲得鎖
if (flag){
//獲得鎖
System.out.println(Thread.currentThread().getName()+"獲得鎖,執行同步代碼塊裡内容");
}else {
System.out.println(Thread.currentThread().getName()+"沒有在規定時間内獲得鎖,執行非同步代碼塊内容");
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (flag){
//如過獲得鎖,要釋放鎖
lock.unlock();
}
}
}
}
公平鎖
含義:誰等的時間最長,誰就先擷取鎖。
預設情況下,ReentrantLock是非公平鎖,可以通過構造方法參數把ReentrantLock聲明為公平鎖
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9_2 {
//在new ReentrantLock時傳入true就可以把lock設定為公平鎖(預設時false)
private static final Lock lock = new ReentrantLock(true);
public void test() {
for (int i = 0; i < 3; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "獲得鎖");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
myThread9_2 m = new myThread9_2();
for (int i = 1; i <= 5; i++) {
//m::test等價于()->m.test();
new Thread(m::test, "線程" + i).start();
}
}
}
ReentrantLock和synchronized的對比
- ReentrantLock和synchronized都是獨占鎖(排他鎖),一個時刻都隻能有一個線程持有該鎖
- ReentrantLock和synchronized都是可重入鎖,一個線程可以重複的獲得已經持有的鎖
- synchronized可以自動獲得鎖和釋放鎖,ReentrantLock需要手動獲得鎖并手動在finally裡釋放鎖
- 在性能上,ReentrantLock是輕量級鎖;但jdk 1.5之後,synchronized加入了鎖更新的概念(偏向鎖,輕量級鎖,重量級鎖),是以二者性能相當.
- synchronized不可響應中斷,一個線程擷取不到鎖就一直等着;ReentrantLock可以響應中斷