您好,登錄后才能下訂單哦!
本文目標:
Object對象中的wait(),notify()方法,用于線程等待和喚醒等待中的線程,大家應該比較熟悉,想再次了解的朋友可以移步到線程的基本操作
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo1 {
static Object lock = new Object();
public static class T1 extends Thread {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備獲取鎖!");
synchronized (lock) {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "獲取鎖成功!");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "釋放鎖成功!");
}
}
public static class T2 extends Thread {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備獲取鎖!");
synchronized (lock) {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "獲取鎖成功!");
lock.notify();
System.out.println(System.currentTimeMillis() + "," + this.getName() + " notify!");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備釋放鎖!");
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "釋放鎖成功!");
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
TimeUnit.SECONDS.sleep(5);
T2 t2 = new T2();
t2.setName("t2");
t2.start();
}
}
輸出:
1:1563530109234,t1準備獲取鎖!
2:1563530109234,t1獲取鎖成功!
3:1563530114236,t2準備獲取鎖!
4:1563530114236,t2獲取鎖成功!
5:1563530114236,t2 notify!
6:1563530119237,t2準備釋放鎖!
7:1563530119237,t2釋放鎖成功!
8:1563530119237,t1釋放鎖成功!
代碼結合輸出的結果我們分析一下:
在了解Condition之前,需要先了解一下重入鎖ReentrantLock,可以移步到:JUC中的ReentranLock。
任何一個java對象都天然繼承于Object類,在線程間實現通信的往往會應用到Object的幾個方法,比如wait()、wait(long timeout)、wait(long timeout, int nanos)與notify()、notifyAll()幾個方法實現等待/通知機制,同樣的, 在java Lock體系下依然會有同樣的方法實現等待/通知機制。
從整體上來看Object的wait和notify/notify是與對象監視器配合完成線程間的等待/通知機制,而Condition與Lock配合完成等待通知機制,前者是java底層級別的,后者是語言級別的,具有更高的可控制性和擴展性。兩者除了在使用方式上不同外,在功能特性上還是有很多的不同:
Condition由ReentrantLock對象創建,并且可以同時創建多個,Condition接口在使用前必須先調用ReentrantLock的lock()方法獲得鎖,之后調用Condition接口的await()將釋放鎖,并且在該Condition上等待,直到有其他線程調用Condition的signal()方法喚醒線程,使用方式和wait()、notify()類似。
示例代碼:
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo2 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備獲取鎖!");
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "獲取鎖成功!");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "釋放鎖成功!");
}
}
public static class T2 extends Thread {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備獲取鎖!");
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + "獲取鎖成功!");
condition.signal();
System.out.println(System.currentTimeMillis() + "," + this.getName() + " signal!");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "準備釋放鎖!");
} finally {
lock.unlock();
}
System.out.println(System.currentTimeMillis() + "," + this.getName() + "釋放鎖成功!");
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
TimeUnit.SECONDS.sleep(5);
T2 t2 = new T2();
t2.setName("t2");
t2.start();
}
}
輸出:
1563532185827,t1準備獲取鎖!
1563532185827,t1獲取鎖成功!
1563532190829,t2準備獲取鎖!
1563532190829,t2獲取鎖成功!
1563532190829,t2 signal!
1563532195829,t2準備釋放鎖!
1563532195829,t2釋放鎖成功!
1563532195829,t1釋放鎖成功!
輸出的結果和使用synchronized關鍵字的實例類似。
Condition.await()方法和Object.wait()方法類似,當使用Condition.await()方法時,需要先獲取Condition對象關聯的ReentrantLock的鎖,在Condition.await()方法被調用時,當前線程會釋放這個鎖,并且當前線程會進行等待(處于阻塞狀態)。在signal()方法被調用后,系統會從Condition對象的等待隊列中喚醒一個線程,一旦線程被喚醒,被喚醒的線程會嘗試重新獲取鎖,一旦獲取成功,就可以繼續執行了。因此,在signal被調用后,一般需要釋放相關的鎖,讓給其他被喚醒的線程,讓他可以繼續執行。
Condition接口提供的常用方法有:
和Object中wait類似的方法
和Object的notify/notifyAll類似的方法
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo4 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
System.out.println("中斷標志:" + this.isInterrupted());
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
TimeUnit.SECONDS.sleep(2);
//給t1線程發送中斷信號
System.out.println("1、t1中斷標志:" + t1.isInterrupted());
t1.interrupt();
System.out.println("2、t1中斷標志:" + t1.isInterrupted());
}
}
輸出:
1、t1中斷標志:false
2、t1中斷標志:true
中斷標志:false
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at com.itsoku.chat09.Demo4$T1.run(Demo4.java:19)
調用condition.await()之后,線程進入阻塞中,調用t1.interrupt(),給t1線程發送中斷信號,await()方法內部會檢測到線程中斷信號,然后觸發InterruptedException
異常,線程中斷標志被清除。從輸出結果中可以看出,線程t1中斷標志的變換過程:false->true->false
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo5 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
boolean r = condition.await(2, TimeUnit.SECONDS);
System.out.println(r);
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
}
}
輸出:
1563541624082,t1,start
false
1563541626085,t1,end
t1線程等待2秒之后,自動返回繼續執行,最后await方法返回false,await返回false表示超時之后自動返回
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo6 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
boolean r = condition.await(5, TimeUnit.SECONDS);
System.out.println(r);
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
//休眠1秒之后,喚醒t1線程
TimeUnit.SECONDS.sleep(1);
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
輸出:
1563542046046,t1,start
true
1563542047048,t1,end
t1線程中調用condition.await(5, TimeUnit.SECONDS);
方法會釋放鎖,等待5秒,主線程休眠1秒,然后獲取鎖,之后調用signal()方法喚醒t1,輸出結果中發現await后過了1秒(1、3行輸出結果的時間差),await方法就返回了,并且返回值是true。true表示await方法超時之前被其他線程喚醒了。
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo7 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
long r = condition.awaitNanos(TimeUnit.SECONDS.toNanos(5));
System.out.println(r);
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
}
}
輸出:
1563542547302,t1,start
-258200
1563542552304,t1,end
awaitNanos參數為納秒,可以調用TimeUnit中的一些方法將時間轉換為納秒。
t1調用await方法等待5秒超時返回,返回結果為負數,表示超時之后返回的。
package com.itsoku.chat09;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class Demo8 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static class T1 extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",start");
long r = condition.awaitNanos(TimeUnit.SECONDS.toNanos(5));
System.out.println(r);
System.out.println(System.currentTimeMillis() + "," + this.getName() + ",end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
t1.setName("t1");
t1.start();
//休眠1秒之后,喚醒t1線程
TimeUnit.SECONDS.sleep(1);
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
輸出:
1563542915991,t1,start
3999988500
1563542916992,t1,end
t1中調用await休眠5秒,主線程休眠1秒之后,調用signal()喚醒線程t1,await方法返回正數,表示返回時距離超時時間還有多久,將近4秒,返回正數表示,線程在超時之前被喚醒了。
其他幾個有參的await方法和無參的await方法一樣,線程調用interrupt()方法時,這些方法都會觸發InterruptedException異常,并且線程的中斷標志會被清除。
使用兩個Condition來實現一個阻塞隊列的例子:
package com.itsoku.chat09;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 微信公眾號:javacode2018,獲取年薪50萬課程
*/
public class BlockingQueueDemo<E> {
int size;//阻塞隊列最大容量
ReentrantLock lock = new ReentrantLock();
LinkedList<E> list = new LinkedList<>();//隊列底層實現
Condition notFull = lock.newCondition();//隊列滿時的等待條件
Condition notEmpty = lock.newCondition();//隊列空時的等待條件
public BlockingQueueDemo(int size) {
this.size = size;
}
public void enqueue(E e) throws InterruptedException {
lock.lock();
try {
while (list.size() == size)//隊列已滿,在notFull條件上等待
notFull.await();
list.add(e);//入隊:加入鏈表末尾
System.out.println("入隊:" + e);
notEmpty.signal(); //通知在notEmpty條件上等待的線程
} finally {
lock.unlock();
}
}
public E dequeue() throws InterruptedException {
E e;
lock.lock();
try {
while (list.size() == 0)//隊列為空,在notEmpty條件上等待
notEmpty.await();
e = list.removeFirst();//出隊:移除鏈表首元素
System.out.println("出隊:" + e);
notFull.signal();//通知在notFull條件上等待的線程
return e;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
BlockingQueueDemo<Integer> queue = new BlockingQueueDemo<>(2);
for (int i = 0; i < 10; i++) {
int data = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
queue.enqueue(data);
} catch (InterruptedException e) {
}
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Integer data = queue.dequeue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
代碼非常容易理解,創建了一個阻塞隊列,大小為3,隊列滿的時候,會被阻塞,等待其他線程去消費,隊列中的元素被消費之后,會喚醒生產者,生產數據進入隊列。上面代碼將隊列大小置為1,可以實現同步阻塞隊列,生產1個元素之后,生產者會被阻塞,待消費者消費隊列中的元素之后,生產者才能繼續工作。
對比項 | Object 監視器方法 | Condition |
---|---|---|
前置條件 | 獲取對象的鎖 | 調用Lock.lock獲取鎖,調用Lock.newCondition()獲取Condition對象 |
調用方式 | 直接調用,如:object.wait() | 直接調用,如:condition.await() |
等待隊列個數 | 一個 | 多個,使用多個condition實現 |
當前線程釋放鎖并進入等待狀態 | 支持 | 支持 |
當前線程釋放鎖進入等待狀態中不響應中斷 | 不支持 | 支持 |
當前線程釋放鎖并進入超時等待狀態 | 支持 | 支持 |
當前線程釋放鎖并進入等待狀態到將來某個時間 | 不支持 | 支持 |
喚醒等待隊列中的一個線程 | 支持 | 支持 |
喚醒等待隊列中的全部線程 | 支持 | 支持 |
void await() throws InterruptedException;
方法會釋放鎖,讓當前線程等待,支持喚醒,支持線程中斷void awaitUninterruptibly();
方法會釋放鎖,讓當前線程等待,支持喚醒,不支持線程中斷long awaitNanos(long nanosTimeout) throws InterruptedException;
參數為納秒,此方法會釋放鎖,讓當前線程等待,支持喚醒,支持中斷。超時之后返回的,結果為負數;超時之前返回的,結果為正數(表示返回時距離超時時間相差的納秒數)boolean await(long time, TimeUnit unit) throws InterruptedException;
方法會釋放鎖,讓當前線程等待,支持喚醒,支持中斷。超時之后返回的,結果為false;超時之前返回的,結果為trueboolean awaitUntil(Date deadline) throws InterruptedException;
參數表示超時的截止時間點,方法會釋放鎖,讓當前線程等待,支持喚醒,支持中斷。超時之后返回的,結果為false;超時之前返回的,結果為truevoid signal();
會喚醒一個等待中的線程,然后被喚醒的線程會被加入同步隊列,去嘗試獲取鎖void signalAll();
會喚醒所有等待中的線程,將所有等待中的線程加入同步隊列,然后去嘗試獲取鎖java高并發系列連載中,總計估計會有四五十篇文章,可以關注公眾號:javacode2018,獲取最新文章。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。