wait和notify方法用来实现一个线程需要等待另一个线程的执行结果的场景。
wait: 让当前线程在Monitor对象上等待,变成Waiting状态
notify:唤醒Monitor对象上等待的一个线程
notifyAll:唤醒Monitor对象上等待的全部线程
一、wait和notify方法的简单使用
假设有两个线程t1,t2,t1需要有烟才能干活,t2是来送烟的。
public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        //是否有烟的标记,默认false
        final boolean[] isHaveSmoke = {false};
        Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    if (!isHaveSmoke[0]){
                        //没有烟时就等待
                        try {
                            //注意要在哪个锁对象上等待就要调用哪个对象的方法
                            //这里是lock上等待
                            //wait方法会释放锁
                            System.out.println("没烟开始等待");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //有烟后继续干活
                    System.out.println("继续干活,烟的状态:"+ isHaveSmoke[0]);
                }
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程t2来送烟,3s后把烟送到
                try {
                    //注意sleep不会释放锁,所以不放在synchronized中,让其他线程有机会执行
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock){
                    System.out.println("烟送到了:");
                    isHaveSmoke[0] =true;
                    lock.notify();
                }
            }
        },"t2");
        
        t1.start();
        t2.start();
        
    }
}
上边这段代码演示了t1等待t2的结果后才能执行。
二、虚假唤醒问题
上边这段代码,如果还有一个线程t3在烟还没送到前就执行了notifyAll方法,t1就会被提前唤醒然后执行后边的逻辑
public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        //是否有烟的标记,默认false
        final boolean[] isHaveSmoke = {false};
        Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    if (!isHaveSmoke[0]){
                        //没有烟时就等待
                        try {
                            //注意要在哪个锁对象上等待就要调用哪个对象的方法
                            //这里是lock上等待
                            //wait方法会释放锁
                            System.out.println("没烟开始等待");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //有烟后继续干活
                    System.out.println("继续干活,烟的状态:"+ isHaveSmoke[0]);
                }
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程t2来送烟,3s后把烟送到
                try {
                    //注意sleep不会释放锁,所以不放在synchronized中,让其他线程有机会执行
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock){
                    System.out.println("烟送到了:");
                    isHaveSmoke[0] =true;
                    lock.notify();
                }
            }
        },"t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //睡1s是为了保证t1已经开始等待了
                    Thread.sleep(1000);
                    synchronized (lock){
                        System.out.println("t3唤醒全部线程");
                        lock.notifyAll();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t3");
        t1.start();
        t2.start();
        t3.start();
    }
}
为了展示烟的状态,我在t1干活时打印了烟的状态,可以看到t1确实是被虚假唤醒的。针对这种问题,因为if判断只有一次机会,所以t1被虚假唤醒后就不能再次判断烟的状态了,所以可以把if判断改成while,这样当t1被虚假唤醒后会再次循环检测烟的状态,如果烟还没到,t1就会继续等待。
Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    while (!isHaveSmoke[0]){
                        //没有烟时就等待
                        try {
                            //注意要在哪个锁对象上等待就要调用哪个对象的方法
                            //这里是lock上等待
                            //wait方法会释放锁
                            System.out.println("没烟开始等待");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //有烟后继续干活
                    System.out.println("继续干活,烟的状态:"+ isHaveSmoke[0]);
                }
            }
        },"t1");
三、wait notify的原理
某个线程必须先获取到锁(称为锁对象对应的Monitor对象的owner)才能调用wait 和notify方法。
wait方法使用到了Monitor对象里的waitset属性,当一个线程调用了wait方法时就会进入Monitor的waitset进行等待,状态变成waiting并释放锁,当其他线程(当前锁对象的owner线程)调用了锁对象的唤醒方法(notify/notifyAll)后,waitSet里的线程就会进入entryList变成Block状态,当owner线程释放锁后entryList里的线程就会开始竞争锁。
四、保护性暂停
这是一种设计模式,用来让一个线程等待另一个线程的结果
public class Test6 {
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("等待结果:");
                Object response = guardedObject.getResponse(2000);
                System.out.println("等到的结果:"+response);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("等待后设置结果");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                guardedObject.setResponse("hello");
            }
        });
        t1.start();
        t2.start();
    }
}
//这个对象用来在两个线程之间传递结果
class GuardedObject{
    private Object response;
    public synchronized void setResponse(Object response){
        this.response =response;
        //唤醒正在等待的线程
        notifyAll();
    }
    //超过一定时间后就不再等待
    public synchronized Object getResponse(long timeout){
        //记录开始等待的时间
        long start = System.currentTimeMillis();
        long pastTime=0;//已经等待的时间
        while (response==null){
            if(pastTime- timeout>=0){
                break;
            }
            try {
                //只等待还剩下的还没等够的时间
                wait(timeout-pastTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //等待结束时计算pastTime
            pastTime=System.currentTimeMillis()-start;
        }
        return response;
    }
}
注意 join方法的底层用的就是wait方法,就用了类似这样的一种等待方式
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
要注意的是join方法被synchronized修饰,wait方法等待的时候就是在当前线程对象绑定的Monitor上等待,和普通对象的情况不一样的是线程在结束
运行前会把其绑定的Monitor上等待的线程全部唤醒,这就是为什么上边join的源码中没有notify方法也能够被唤醒的原因
                    
                
                
            
        
浙公网安备 33010602011771号