本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,如果大家想獲取最新教程,請關注微信公眾號,謝謝!
在多執行緒中有個wait()方法,它的作用是使當前執行程式碼的執行緒進行等待,wait()方法是Object類的方法,該方法用來將當前執行緒置入“預執行佇列”中,並且在wait()所在的程式碼行處停止執行,直到接到通知或被中斷為止。在呼叫wait()之前,執行緒必須獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫wait()方法。
有了等待就有喚醒,方法是notify(),notify()方法需要在同步方法或同步程式碼塊中呼叫,呼叫前,執行緒必須獲得該物件的物件級別鎖。通過這個方法可以通知那些可能等待物件的物件鎖的其他執行緒,如果多個執行緒等待,則由執行緒規劃器隨機挑選出其中一個呈wait狀態的執行緒,對其發出通知notify,並使它等待獲取該物件的物件鎖。在執行notify()方法後,當前執行緒不會馬上釋放該物件鎖,呈wait狀態的執行緒也並不能馬上獲取該物件鎖,要等到執行notify()方法的執行緒將程式執行完,也就是退出synchronized程式碼塊後,當前執行緒才會釋放鎖,而呈wait狀態所在的執行緒才可以獲取該物件鎖。
當第一個獲得了該物件鎖的wait執行緒執行完畢後,它會釋放掉該物件鎖,此時如果該物件沒有再次使用notify語句,則即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,還會繼續阻塞在wait狀態,直到這個物件發出一個notify或notifyAll。
為了驗證wait()和notify()兩個方法的作用,請看下面程式碼:
public class ThreadFirst extends Thread {
private Object mLock;
public ThreadFirst(Object o) {
this.mLock = o;
}
@Override
public void run() {
super.run();
try {
synchronized (mLock) {
System.out.println("第一個執行緒開始");
mLock.wait();
System.out.println("第一個執行緒結束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製程式碼
在第一個執行緒中建立了mLock為物件鎖的同步程式碼塊,內部呼叫mLock.wait()使當前執行緒進行等待。
public class ThreadSecond extends Thread {
private Object mLock;
public ThreadSecond(Object o) {
this.mLock = o;
}
@Override
public void run() {
super.run();
synchronized (mLock) {
System.out.println("第二執行緒開始");
mLock.notify();
System.out.println("第二執行緒結束");
}
}
}
複製程式碼
第二個執行緒在同步程式碼塊中通過notify()喚醒持有mLock物件鎖的執行緒,執行notify()方法後,第二個執行緒不會馬上釋放該物件的鎖,第一個執行緒不能馬上獲取mLock物件鎖,要等到執行notify()方法的第二個執行緒中的synchronized程式碼塊執行完,第一個執行緒才能獲取該物件鎖,繼續執行wait()方法後的程式碼。
public class Client {
public static void main(String[] args) {
//建立物件鎖
Object lock=new Object();
//建立第一個執行緒(wait)
ThreadFirst threadFirst=new ThreadFirst(lock);
threadFirst.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//建立第二個執行緒(notify)
ThreadSecond threadSecond=new ThreadSecond(lock);
threadSecond.start();
}
}
複製程式碼
先建立一個物件鎖,使得兩個執行緒都持有相同的物件鎖,先執行執行緒1進入wait狀態,再執行執行緒2通過notify()喚醒執行緒1的synchronized程式碼塊。
列印結果:
第一個執行緒開始
第二執行緒開始
第二執行緒結束
第一個執行緒結束
複製程式碼
wait()方法可以使呼叫該方法的執行緒釋放共享資源的鎖,然後從執行狀態退出,進入等待佇列,直到被再次喚醒。notify()方法可以隨機喚醒等待佇列中等待同一共享資源的“一個”執行緒,並使該執行緒退出等待佇列,進入可執行狀態,notify()方法僅通知一個執行緒,而notifyAll()方法可以使所有正在等待佇列中等待同一共享資源的全部執行緒從等待狀態退出,進入可執行狀態,此時,優先順序最高的那個執行緒最先執行,但也有可能是隨機執行,取決於JVM虛擬機器的實現。
每個鎖物件都有兩個佇列,一個是就緒佇列,一個是阻塞佇列,就緒佇列儲存了將要獲得鎖的執行緒,阻塞佇列儲存了被阻塞的執行緒。一個執行緒被喚醒後,才會進入就緒佇列,等待CPU的排程,反之,一個執行緒被wait後,就會進入阻塞佇列,等待下一次被喚醒。
有沒有想過,如果當前的執行緒呈wait狀態,這時呼叫執行緒物件的interrupt()方法會出現什麼情況,修改Client程式碼如下:
public class Client {
public static void main(String[] args) {
//建立物件鎖
Object lock=new Object();
//建立第一個執行緒(wait)
ThreadFirst threadFirst=new ThreadFirst(lock);
threadFirst.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadFirst.interrupt();
}
}
複製程式碼
列印:
第一個執行緒開始
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.book.demo.demo01.ThreadFirst.run(ThreadFirst.java:17)
複製程式碼
ThreadFirst啟動後,在run方法中呼叫wait()方法,使當前的執行緒處於wait狀態,這時執行該執行緒物件的interrupt方法會出現InterruptedException異常。
除了wait()方法外,還有一個帶引數的wait(long)方法,它的作用是等待某一時間內是否有執行緒對鎖進行喚醒,如果超過這個時間則自動喚醒,看下面程式碼:
public class ThreadFirst extends Thread {
private Object mLock;
public ThreadFirst(Object o) {
this.mLock = o;
}
@Override
public void run() {
super.run();
try {
synchronized (mLock) {
System.out.println("第一個執行緒開始");
mLock.wait(3000);
System.out.println("第一個執行緒結束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製程式碼
Client程式碼:
public class Client {
public static void main(String[] args) {
//建立物件鎖
Object lock=new Object();
//建立第一個執行緒(wait)
ThreadFirst threadFirst=new ThreadFirst(lock);
threadFirst.start();
}
}
複製程式碼
列印:
第一個執行緒開始
第一個執行緒結束
複製程式碼
呼叫wait方法時,指定了超過3秒會自動喚醒,繼續執行下面的列印語句。
Java提供了各種各樣的輸入/輸出流Stream,可以很方便地對資料進行操作,其中管道流是一種特殊的流,用於在不同執行緒間直接傳送資料,一個執行緒傳送資料到輸出管道,另一個執行緒從輸入管道中讀取資料,Java提供了4種類來進行執行緒間通訊,分別是PipedInputStream和PipedOutputStream、PipedReader和PipedWriter。
PipedInputStream和PipedOutputStream程式碼示例如下:
public class WriteBean {
public void write(PipedOutputStream outputStream){
if(null==outputStream){
return;
}
System.out.println("開始寫入資料...");
try {
outputStream.write("你好,我叫寫資料".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼
WriteBean這個類提供一個write方法傳入PipedOutputStream物件用於資料的寫入。
public class ReadBean {
public void read(PipedInputStream inputStream) {
if (null == inputStream) {
return;
}
System.out.println("開始讀取資料");
byte[] bytes = new byte[30];
try {
int length = inputStream.read(bytes);
while (length != -1) {
String data = new String(bytes, 0, length);
System.out.println(data);
length = inputStream.read(bytes);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼
ReadBean類提供了一個read方法,傳入PipedInputStream物件用於資料的讀取。
建立一個寫程式的類:
public class WriteThread extends Thread {
private PipedOutputStream mOutputStream;
private WriteBean mWriteBean;
public WriteThread(WriteBean writeBean,PipedOutputStream outputStream){
this.mWriteBean=writeBean;
this.mOutputStream=outputStream;
}
@Override
public void run() {
super.run();
if(mWriteBean!=null) {
mWriteBean.write(mOutputStream);
}
}
}
複製程式碼
建立一個讀程式的類:
public class ReadThread extends Thread {
private PipedInputStream mInputStream;
private ReadBean mReadBean;
public ReadThread(PipedInputStream inputStream, ReadBean mReadBean) {
this.mInputStream = inputStream;
this.mReadBean = mReadBean;
}
@Override
public void run() {
super.run();
if (mReadBean != null) {
mReadBean.read(mInputStream);
}
}
}
複製程式碼
Client程式碼:
public class Client {
public static void main(String[] args) {
PipedOutputStream outputStream=new PipedOutputStream();
WriteBean writeBean=new WriteBean();
PipedInputStream inputStream=new PipedInputStream();
ReadBean readBean=new ReadBean();
try {
//將管道輸出與輸入流的相互關聯
outputStream.connect(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
//先開啟讀程式
ReadThread readThread=new ReadThread(inputStream,readBean);
readThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//開啟寫程式
WriteThread writeThread=new WriteThread(writeBean,outputStream);
writeThread.start();
}
}
複製程式碼
列印:
開始讀取資料
開始寫入資料...
你好,我叫寫資料
複製程式碼
在讀取執行緒被啟動後,由於當時沒有資料寫入,所以執行緒阻塞在inputStream.read(bytes)這段程式碼,當資料寫入後,才會繼續執行讀取執行緒下面的程式碼。
PipedReader和PipedWriter程式碼就不貼出來了,寫法與PipedInputStream和PipedOutputStream相似。