本篇文章已授權微信公眾號 顧林海 獨家釋出
在JDK1.5中新增ReentrantLock類,效果類似於使用synchronized關鍵字實現執行緒間同步互斥,並且在擴充套件功能上也更加強大,比如具有嗅探鎖定、多路分支通知等功能。看下面ReentrantLock的例項程式碼:
public class Task {
private Lock mLock=new ReentrantLock();
public void runTask(){
mLock.lock();
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
mLock.unlock();
}
}
複製程式碼
利用ReentrantLock物件的lock()方法獲取鎖,通過unlock()方法釋放鎖。
public class ThreadFirst extends Thread {
private Task mTask;
public ThreadFirst(Task task){
this.mTask=task;
}
@Override
public void run() {
super.run();
mTask.runTask();
}
}
複製程式碼
Client:
public class Client {
public static void main(String[] args) {
Task task=new Task();
ThreadFirst threadFirst;
for (int i = 0; i < 3; i++) {
threadFirst=new ThreadFirst(task);
threadFirst.start();
}
}
}
複製程式碼
列印:
Thread-1-->0
Thread-1-->1
Thread-1-->2
Thread-2-->0
Thread-2-->1
Thread-2-->2
Thread-0-->0
Thread-0-->1
Thread-0-->2
複製程式碼
從列印結果看,列印是按分組來列印的,當前執行緒列印完畢後釋放鎖,其他執行緒才可以繼續列印,
在前面文章中可以通過synchronized與wait()和notify()/notifyAll()方法相結合實現等待/通知模式,利用ReentrantLock也可以實現同樣的效果,但需要藉助於Condition物件。Condition可以實現多路通知功能,可以在一個Lock物件裡面建立多個Condition例項,執行緒物件可以註冊在指定的Condition中,從而可以有選擇性進行執行緒通知。
程式碼示例:
public class Task {
private Lock mLock=new ReentrantLock();
private Condition mCondition=mLock.newCondition();
public void runTask(){
try {
mLock.lock();
System.out.println("列印一些資訊:A");
mCondition.await();
System.out.println("列印剩餘資訊:B");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
mLock.unlock();
System.out.println("釋放鎖");
}
}
}
複製程式碼
執行緒類:
public class ThreadFirst extends Thread {
private Task mTask;
public ThreadFirst(Task task){
this.mTask=task;
}
@Override
public void run() {
super.run();
mTask.runTask();
}
}
複製程式碼
Client:
public class Client {
public static void main(String[] args) {
Task task=new Task();
ThreadFirst threadFirst=new ThreadFirst(task);
threadFirst.start();
}
}
複製程式碼
列印:
列印一些資訊:A
複製程式碼
從列印結果看只列印了A,後面B沒列印出來,這是因為呼叫了Condition物件的await()方法,使當前執行任務的執行緒進入等待WAITING狀態。
補齊上面程式碼,在Task類中新新增一個notifyTask方法:
public void notifyTask(){
try {
mLock.lock();
System.out.println("通知task繼續執行");
mCondition.signal();
}finally {
mLock.unlock();
}
}
複製程式碼
Client修改如下:
public class Client {
public static void main(String[] args) {
Task task=new Task();
ThreadFirst threadFirst=new ThreadFirst(task);
threadFirst.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
task.notifyTask();
}
}
複製程式碼
列印:
列印一些資訊:A
通知task繼續執行
列印剩餘資訊:B
釋放鎖
複製程式碼
到這裡可以發現Object類的wait()和wait(long)方法相當於Condition類中的await()和await(long)方法。notify()和notifyAll相當於Condition類中的signal()和signalAll()方法。
當然我們也可以建立多個Condition物件來實現通知部分執行緒,程式碼如下:
public class Task {
private Lock mLock = new ReentrantLock();
private Condition mConditionA = mLock.newCondition();
private Condition mConditionB = mLock.newCondition();
public void runTaskA() {
try {
mLock.lock();
System.out.println("列印一些資訊:A");
mConditionA.await();
System.out.println("列印剩餘資訊:A");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
System.out.println("釋放鎖A");
}
}
public void runTaskB() {
try {
mLock.lock();
System.out.println("列印一些資訊:B");
mConditionB.await();
System.out.println("列印剩餘資訊:B");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
System.out.println("釋放鎖B");
}
}
public void notifyTaskA() {
try {
mLock.lock();
System.out.println("通知A繼續執行");
mConditionA.signal();
} finally {
mLock.unlock();
}
}
public void notifyTaskB() {
try {
mLock.lock();
System.out.println("通知B繼續執行");
mConditionB.signal();
} finally {
mLock.unlock();
}
}
}
複製程式碼
執行緒類1:
public class ThreadFirst extends Thread {
private Task mTask;
public ThreadFirst(Task task){
this.mTask=task;
}
@Override
public void run() {
super.run();
mTask.runTaskA();
}
}
複製程式碼
執行緒類2:
public class ThreadSecond extends Thread {
private Task mTask;
public ThreadSecond(Task task){
this.mTask=task;
}
@Override
public void run() {
super.run();
mTask.runTaskB();
}
}
複製程式碼
Client:
public class Client {
public static void main(String[] args) {
Task task=new Task();
ThreadFirst threadFirst=new ThreadFirst(task);
ThreadSecond threadSecond=new ThreadSecond(task);
threadFirst.start();
threadSecond.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
task.notifyTaskA();
}
}
複製程式碼
列印:
列印一些資訊:A
列印一些資訊:B
通知A繼續執行
列印剩餘資訊:A
釋放鎖A
複製程式碼
從列印結果可以看出,使用ReentrantLock物件可以喚醒指定種類的執行緒,非常方便。
鎖Lock分為“公平鎖”和“非公平鎖”,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先到先得的FIFO先進先出順序,而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得的,和公平鎖不一樣的就是先來的不一定先得到鎖,這樣的話可能造成某些執行緒一直拿不到鎖。