什么是等待通知機制
在單線程中,要執行的操作需要滿足一定條件才能執行,可以把這個操作放在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)方法,在指定時間內沒有操作會被自動喚醒