什麼是等待通知機制
在單執行緒中,要執行的操作需要滿足一定條件才能執行,可以把這個操作放在if語句塊中。
在多執行緒程式設計中,可能A執行緒的條件沒有滿足只是暫時的,稍後其他的執行緒B可能會更新條件使得A執行緒的條件得以滿足,可以將A執行緒暫停,直到它的條件得到滿足之後再將A執行緒喚醒
Atomic{
while(條件不成立)
{
等待
}
條件滿足後,當前執行緒被喚醒
}
等待通知機制的實現
object類中的Wait方法可以使當前執行緒的程式碼暫停執行,直到接到通知或者被中斷為止
注意:
(1)wait方法只能再同步程式碼塊中由鎖物件呼叫
(2)呼叫wait方法,當前執行緒會釋放鎖
public class Text16_5 {
public static void main(String[] args) throws InterruptedException {
String text="hello";
System.out.println("同步前程式碼塊");
synchronized (text)
{
System.out.println("同步程式碼塊開始");
text.wait();
System.out.println("同步程式碼塊結束");
}
System.out.println("全部結束");
}
}
因為呼叫了鎖物件的wait方法,會釋放鎖物件,處於等待的狀態,沒有被喚醒就會一直等待下去。
object類的notify方法可以喚醒執行緒,該方法也必須同步在程式碼塊中,由鎖物件呼叫,沒有使用鎖物件呼叫wait/notify會報出IIegalMonuitorStateExeption異常,如果由多個等待的執行緒,notify方法只能喚醒其中的一個,在同步程式碼塊中呼叫notify方法後,並不會立即釋放鎖物件,需要等當前同步程式碼塊執行完後才會釋放鎖物件,一般將notify放在同步程式碼塊最後。
synchronized(鎖物件)
{
//執行修改保護條件的程式碼
//喚醒其他執行緒
鎖物件.notify();
}
public class TextNotify {
public static void main(String[] args) throws InterruptedException {
String text="hello";
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (text)
{
System.out.println("同步程式碼塊開始");
try {
text.wait();//執行緒等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步程式碼塊結束");
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
synchronized (text)
{
System.out.println("執行緒開始喚醒");
text.notify();
System.out.println("執行緒結束喚醒");
}
}
});
t1.start();//開啟t1執行緒 t1等待
Thread.sleep(3000);//睡眠3秒 確保t1處於等待狀態
t2.start();//喚醒t1執行緒
}
}
notify不會立即釋放鎖物件
案例:
import java.util.ArrayList;
import java.util.List;
public class NotifyText2 {
public static void main(String[] args) throws InterruptedException {
List<String> strings=new ArrayList<>();
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (strings)
{
System.out.println("執行緒1開始等待");
try {
strings.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒1被喚醒");
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
synchronized (strings)
{
for (int i = 0; i <10 ; i++) {
strings.add("data"+i);
System.out.println("執行緒2新增了"+(i+1));
if(strings.size()==5)
{
strings.notify();
System.out.println("執行緒2被喚醒");
}
}
}
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}
}
執行緒2的程式碼還沒有執行完畢,鎖沒有立即釋放依然在執行,需要等到所有程式碼塊全部執行完畢才釋放
interrupt會中斷執行緒的等待
public class InterruptText {
private static final String name=new String();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (name)
{
try {
System.out.println("同步程式碼塊開始");
name.wait();
System.out.println("同步程式碼塊結束");
} catch (InterruptedException e) {
System.out.println("wait被中斷"+e);
}
}
}
});
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
原來鎖物件需要執行完同步程式碼塊才能釋放鎖物件,在執行過程如果遇到異常也會導致執行緒終止,釋放鎖物件。呼叫wait方法也會釋放鎖物件。
notify與notifyAll的區別
notify一次只能喚醒一個,如果有多個執行緒都在等待,只能隨機喚醒其中的一個,想要喚醒所有等待執行緒需要呼叫notifyAll。
public class InterruptText {
private static final String name=new String();
public static void main(String[] args) throws InterruptedException {
String str=new String();
NotifyAll notifyAll=new NotifyAll(str);
NotifyAll notifyAl2=new NotifyAll(str);
NotifyAll notifyAll3=new NotifyAll(str);
notifyAll.setName("執行緒一");
notifyAl2.setName("執行緒二");
notifyAll3.setName("執行緒三");
notifyAll.start();
notifyAl2.start();
notifyAll3.start();
Thread.sleep(2000);//休眠兩秒
synchronized (str)
{
//str.notify();只能隨機喚醒一個
str.notifyAll();//喚醒全部執行緒
}
};
static class NotifyAll extends Thread
{
private String name;
private NotifyAll(String name)
{
this.name=name;
}
@Override
public void run() {
synchronized (name)
{
try {
System.out.println(Thread.currentThread().getName()+"同步程式碼塊開始");
name.wait();
System.out.println(Thread.currentThread().getName()+"同步程式碼塊結束");
} catch (InterruptedException e) {
System.out.println("wait被中斷"+e);
}
}
}
}
}
如果只呼叫一次notify()之惡能喚醒其中的一個執行緒,其他等待執行緒依然處於等待狀態,就錯過了通知訊號,這種現象稱之為訊號丟失。
wait(Long)的使用
帶有引數的Wait(Long)方法,在指定時間內沒有操作會被自動喚醒