關鍵字syncronized與wait()和notify()和notifyAll()可以實現等待通知,類ReentrantLock也可以實現同樣的功能,但需要藉助於Condition,這個類可以實現多路通知,也就是說在一個Lock對象裏可以創建多個Condition實例(對象監視器),線程對象可以註冊在指定的Condition中,從而可以選擇性的通知線程在線程調度上更加靈活。
而syncronized就相等於整個Lock對象中只有單一的一個Condition對象,所有的線程都註冊在它一個上面,線程開始notifyAll()時,需要通知所有的WAITING線程,沒有選擇權,就會出現資源浪費。下面一個Demo演示的是Condition的錯誤用法。
public class Demo1 {
private Lock lock = new ReentrantLock ();
private Condition condition = lock.newCondition ();
public void await(){
try {
condition.await ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1 ();
new Thread (() -> {
demo1.await ();
}).start ();
}
}
運行後報錯:因爲在調用Condition.awiat()或single()方法之前需要調用lock.lock()獲得同步監視器,切記,跟我們在使用wait方法是同時需要使用synctonized關鍵字一樣一個道理
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at Lock.KodyLockTest.ConditionStudy.Demo1.await(Demo1.java:19)
at Lock.KodyLockTest.ConditionStudy.Demo1.lambda$main$0(Demo1.java:28)
at java.lang.Thread.run(Thread.java:748)
雙線程通信案例
public class Demo1 {
private Lock lock = new ReentrantLock ();
private Condition condition = lock.newCondition ();
public void await(){
try {
lock.lock ();
System.out.println (Thread.currentThread ().getName () + "線程獲得鎖");
condition.await ();
} catch (InterruptedException e) {
e.printStackTrace ();
}finally {
System.out.println (Thread.currentThread ().getName () + "線程釋放鎖");
lock.unlock ();
}
}
public void single(){
lock.lock ();
System.out.println (Thread.currentThread ().getName () + "線程獲得鎖");
condition.signal ();
System.out.println (Thread.currentThread ().getName () + "線程喚醒睡眠線程");
lock.unlock ();
System.out.println (Thread.currentThread ().getName () + "線程釋放鎖");
}
public static void main(String[] args) throws InterruptedException{
Demo1 demo1 = new Demo1 ();
new Thread (() -> {
demo1.await ();
}).start ();
TimeUnit.SECONDS.sleep (3);
new Thread (() -> {
demo1.single ();
}).start ();
}
}
控制檯信息:首先線程0獲得鎖後進入等待(意味着放棄了lock對象鎖),而後被線程1搶得,線程1接下來喚醒了等待池中等待的線程,此處值得是註冊在condition上的線程,目前就只有一個線程0,所以會被喚醒,由於喚醒了線程0但是喚醒了你要繼續執行線程0的run代碼需要再次獲得lock對象鎖,這就得看線程1釋放沒釋放了,釋放了的話線程0繼續搶鎖,搶到的話執行完run代碼最後也釋放。這裏需要記住的一點就是線程1喚醒線程0並不能立即執行run代碼,因爲你需要再次搶鎖切記,需要拿到鎖才能被喚醒。
Thread-0線程獲得鎖
Thread-1線程獲得鎖
Thread-1線程喚醒睡眠線程
Thread-1線程釋放鎖
Thread-0線程釋放鎖
雙線程交替執行
使用一個Condition對象加一個標識符來實現交替運行
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description:線程一打印循環打印 我愛你 線程2打印 肖體秀
*/
public class Demo2 {
private static Lock lock = new ReentrantLock ();
private static Condition condition = lock.newCondition ();
//設置一個標識符,如果某個線程打印了則修改標識符位,讓自身處於等待,同理另一個線程可以打印
private static Boolean flag = false;//因爲用了線程同步,所以該字面修改是線程可見的
public static void main(String[] args) {
new Thread (() -> {
lock.lock ();
for (int i=0;i<100;i++){
try {
while (flag == true){
condition.await ();
}
System.out.println ("我愛你");
TimeUnit.SECONDS.sleep (1);
flag = true;
condition.signal ();
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
new Thread (() -> {
lock.lock ();
for (int i=0;i<100;i++){
try {
while (flag == false){
condition.await ();
}
System.out.println ("肖體秀");
TimeUnit.SECONDS.sleep (1);
flag = false;
condition.signal ();
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
}
}
使用兩個Condition對象實現,因爲要結束線程聲明週期,所以加了個!=10的判斷,直接釋放鎖,而不是還讓線程處於等待
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description:線程一打印循環打印 我愛你 線程2打印 肖體秀
*/
public class Demo3 {
private static Lock lock = new ReentrantLock ();
private static Condition conditionA = lock.newCondition ();
private static Condition conditionB = lock.newCondition ();
private static AtomicInteger atomicIntegerA = new AtomicInteger (0);
private static AtomicInteger atomicIntegerB = new AtomicInteger (0);
public static void main(String[] args) {
new Thread (() -> {
lock.lock ();
for (int i=0;i<10;i++){
try {
TimeUnit.SECONDS.sleep (1);
System.out.println ("我愛你");
conditionB.signal ();//在進入等待池之前先喚醒B,此時A的鎖並沒有釋放
if ( atomicIntegerA.incrementAndGet () != 10){
conditionA.await ();//如果不是最後一個需要你睡眠,是最後一次的話就直接unlock
}
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
new Thread (() -> {
lock.lock ();
for (int i=0;i<10;i++){
try {
TimeUnit.SECONDS.sleep (1);
System.out.println ("肖體秀");
conditionA.signal ();//在進入等待池之前先喚醒A,此時B的鎖並沒有釋放
if ( atomicIntegerB.incrementAndGet () != 10){
conditionB.await ();
}
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
}
}
三個及以上線程交替執行與順序執行
交替執行案例可以參考我的:https://blog.csdn.net/qq_40826106/article/details/86607122 案例7 這裏我沒做線程結束判斷,自己手動加下就行,那順序執行也是一樣的。
這裏隨機運行三個線程,通過變量num去指定那個線程順序執行:三個線程隨機啓動,假設B先啓動,B會處於阻塞,假設然後C啓動,C也會處於阻塞,當運行到A啓動時,會喚醒B,B執行完喚醒C,以此達到ABC順序運行。
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description: 線程順序執行
*/
public class Demo4 {
private static Lock lock = new ReentrantLock ();
private static Condition conditionA = lock.newCondition ();
private static Condition conditionB = lock.newCondition ();
private static Condition conditionC = lock.newCondition ();
private static int num = 1;
public static void main(String[] args) {
Thread threadA = new Thread (() -> {
try {
lock.lock ();
while (num != 1) {
conditionA.await ();
}
System.out.println ("我愛你肖體秀1");
num = 2;//事情做完了,就賦值爲2
conditionB.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
Thread threadB = new Thread (() -> {
try {
lock.lock ();
while (num != 2) {
conditionB.await ();
}
System.out.println ("我愛你肖體秀2");
num = 3;
conditionC.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
Thread threadC = new Thread (() -> {
try {
lock.lock ();
while (num != 3) {
conditionC.await ();
}
System.out.println ("我愛你肖體秀3");
num = 3;
conditionA.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
threadB.start ();
threadA.start ();
threadC.start ();
}
}