等待和通知机制

1. 等待和通知机制的实现

wait() 方法

wait() 是 Object 类的方法,它的作用是使当前执行代码的线程进行等待,该方法将当前线程置入“预执行队列”中,并在 wait() 所在的代码行处停止执行,直到接到通知或者被中断才能继续执行。当前线程必须获得指定对象的对象锁,即只能在同步方法或者同步方法块中调用 wait() 方法,在执行 wait() 方法后,当前线程释放所拥有的对象锁,如果 wait() 没有持有对象锁就执行,会抛出异常

notify() 方法

notify() 是 Object 类的方法,作用是使停止的线程继续运行,也要在同步方法或者同步块中调用。该方法用来通知那些可能等待指定对象的 对象锁 的其他线程,如果有多个线程等待,则由线程规划器随机挑选处一个呈 wait 状态的线程,对其发出通知 notify,但是被通知的线程不会马上执行 wait 后面的代码,因为使用 notify 的线程不会马上释放锁,所以被通知的线程也不会马上得到锁。如果调用 notify 时没有持有对象锁,就会抛出异常

使用 wait() 和 notify() 方法进行测试

class MyThread1 extends Thread {

    private Object lock;

    public MyThread1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " 开始 wait time = " + System.currentTimeMillis());
            try {
                //wait 使线程 thread1 停止运行
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + " 结束 wait time = " + System.currentTimeMillis());
        }
    }
}

class MyThread2 extends Thread {

    private Object lock;

    public MyThread2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " 开始 notify time = " + System.currentTimeMillis());
            //线程 thread2 使停止的 thread1 继续运行
            lock.notify();
            System.out.println(Thread.currentThread().getName()
                    + " 结束 notify time = " + System.currentTimeMillis());
        }
    }
    
}

public class Test extends Thread {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        MyThread1 thread1 = new MyThread1(lock);
        thread1.start();
        Thread.sleep(2000);
        MyThread2 thread2 = new MyThread2(lock);
        thread2.start();
    }
    
}

结果是:

AAA 开始 wait time = 1540528981658
BBB 开始 notify time = 1540528983660
BBB 结束 notify time = 1540528983661
AAA 结束 wait time = 1540528983661

简单分析一下整个过程,线程 AAA 先执行 run() 方法,获得了 lock 对象锁,输出一行之后执行 lock.wait() 方法,表示线程 AAA 释放对象 lock,然后持有该对象锁的线程 AAA 进入等待状态,但是线程 AAA 依然在 synchronized 同步块中;
由于线程 AAA 停止了,此时线程 BBB 开始执行 run() 方法,获取 lock 对象锁,输出一行之后,调用 lock.notify() 唤醒正在等待对象锁 lock 的线程 AAA,使其进入就绪状态,但线程 BBB 并不马上释放对象锁 lock,而是继续执行自己同步方法中的剩余方法。只有当线程 BBB 执行完 syncrhonized 同步块之后,才释放对象锁,此时被唤醒的线程 AAA 才可以重新获得该对象锁,然后执行 wait() 方法之后的代码

整个过程如图所示,简单来说,就是线程 AAA 被 wait,然后线程 BBB 使用 notify 唤醒线程 AAA,但是不立即释放锁,直到线程 BBB 执行完同步块之后,才释放锁,此时线程 AAA 得到锁,开始执行 wait 后的方法

有一点需要注意,wait() 方法是使 拥有对象锁的那个线程暂时等待,而与谁是那个对象锁没有关系,就像这个例子中,线程 AAA 拥有 Object 对象的对象锁 lock,同时在 run() 方法中用对象锁 lock 调用 wait() 方法,然后对象锁 lock 被释放,注意,是线程 AAA 持有的对象锁 lock被释放,因为线程 AAA 在同步块中,随即造成的是,线程 AAA 进入等待状态。这一点很重要!!!

notify()只能唤醒一个线程

如果有多个线程处于等待状态,那么 notify() 方法只能随机唤醒一个线程,其他没有没唤醒的线程依旧处于等待状态。但是可以多次调用 notity() 方法来随机唤醒多个线程

//Service2 方法
class Service2 {

    public void testMethod(Object lock) {

        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " beg1in wait " + System.currentTimeMillis());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + " end wait " + System.currentTimeMillis());
        }

    }

}

创建三个线程 ThreadA6、ThreadB6 和 ThreadC6

class ThreadA6 extends Thread {

    private Object lock;

    public ThreadA6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }

}

class ThreadB6 extends Thread {

    private Object lock;

    public ThreadB6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }

}

class ThreadC6 extends Thread {

    private Object lock;

    public ThreadC6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }
}

随机唤醒一个正在等待的线程的方法

class NotifyOne extends Thread {

    private Object lock;

    public NotifyOne(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("NotifyOne");
            lock.notify();
        }
    }
}

多次调用可以随机唤醒多个正在等待的线程的方法

class NotifyMulti extends Thread {

    private Object lock;

    public NotifyMulti(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("NotifyMulti");
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    }
}

测试方法

public class Test2 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA6 threadA6 = new ThreadA6(lock);
        threadA6.start();
        ThreadB6 threadB6 = new ThreadB6(lock);
        threadB6.start();
        ThreadC6 threadC6 = new ThreadC6(lock);
        threadC6.start();

        Thread.sleep(2000);

        NotifyOne notifyOne = new NotifyOne(lock);
        notifyOne.start();
       	/*NotifyMulti notifyMulti = new NotifyMulti(lock);
        notifyMulti.start();*/
    }

}

结果是:

Thread-0 beg1in wait 1540536524678
Thread-1 beg1in wait 1540536524679
Thread-2 beg1in wait 1540536524679
notifyOne 唤醒了一个线程 1540536526679
Thread-0 end wait 1540536526679

由于只能唤醒一个线程,另外两个还处于等待状态的线程因为没有被唤醒,就处于永远等待的状态了

如果调用后面被注释的语句,那么就能唤醒多个线程了

Thread-0 beg1in wait 1540536666626
Thread-2 beg1in wait 1540536666626
Thread-1 beg1in wait 1540536666626
NotifyMulti
Thread-0 end wait 1540536668627
Thread-1 end wait 1540536668627
Thread-2 end wait 1540536668627

notifyAll()可以唤醒多个线程

由于不知道有多少个线程处于等待的状态,我们也不可能一直不停调用 notify() 方法,这样会很麻烦,因此可以使用 notifyAll() 方法唤醒全部正在等待的方法

2. 当interrupt方法遇到wait方法

当线程呈 wait() 状态时,调用线程对象的 interrupt() 方法会出现 InterruptedException 异常

class Service5 {

    public void testMethod(Object lock) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " be1gin wait "
                + System.currentTimeMillis());
            try {
                lock.wait();
                System.out.println(Thread.currentThread().getName() + " end wait "
                        + System.currentTimeMillis());
            } catch (InterruptedException e) {
                System.out.println("发生异常...");
                e.printStackTrace();
            }
        }
    }

    public void testMethod2(Object lock) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " begin 2");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " end 2");
        }
    }

}

class ThreadB5 extends Thread {

    private Object lock;

    public ThreadB5(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service5 service5 = new Service5();
        service5.testMethod2(lock);
    }
}

public class ThreadA5 extends Thread {

    private Object lock;

    public ThreadA5(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service5 service5 = new Service5();
        service5.testMethod(lock);
    }

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA5 threadA5 = new ThreadA5(lock);
        threadA5.start();
        threadA5.interrupt();
        Thread.sleep(2000);

        ThreadB5 threadB5 = new ThreadB5(lock);
        threadB5.start();
    }
}

结果是:

Thread-0 be1gin wait 1540534325308
发生异常...
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at edu.just.Service5.testMethod(ThreadA5.java:10)
	at edu.just.ThreadA5.run(ThreadA5.java:60)
Thread-1 begin 2
Thread-1 end 2

可以看到,报错了,同时还执行了 wait() 方法之后的代码,这是因为:在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放,此时如果还有线程持有对象锁,那么 wait 后面的代码将不会执行,而是直接报错

3. 通知时间

wait(long)方法

如果 wait() 方法里面带有参数,表示在一段时间内,如果没有其他线程对等待的线程进行唤醒,那么等待的线程在超过这个时间之后会自动唤醒

  1. 如果在规定时间之内就被唤醒,那么会先执行其他线程的代码,然后在执行 wait 之后的代码
  2. 如果在规定直接之外被唤醒,那么就会先执行 wait 之后代码,在执行其他线程的代码
public class MyRunnable {

    private static Object lock = new Object();

    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {

                System.out.println(Thread.currentThread().getName()
                        + " wait begin time " + System.currentTimeMillis());
                try {
                    //规定 1s 之后线程自动被唤醒
                    lock.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + " wait end time " + System.currentTimeMillis());
            }
        }
    };

    private static Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {

                System.out.println(Thread.currentThread().getName()
                        + " wait begin time2 " + System.currentTimeMillis());
                try {
                    lock.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + " wait end time " + System.currentTimeMillis());
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(runnable);
        thread.setName("AAA");
        thread.start();
		//在规定时间之内线程 AAA 被唤醒		语句1
        Thread.sleep(1100);
		//在规定时间之外线程 AAA 被唤醒     语句2
//        Thread.sleep(900);
        Thread thread2 = new Thread(runnable1);
        thread2.setName("BBB");
        thread2.start();
    }

}

先把语句2注释掉,结果是:

AAA wait begin time 1540538435802
AAA wait end time 1540538436802
BBB wait begin time2 1540538436902
BBB wait end time 1540538436903

看到此时线程 AAA 在被线程 BBB 手动唤醒之前就自动唤醒,所以直接执行了 wait 后面的方法

在执行语句2,把语句1注释,结果是:

AAA wait begin time 1540538528885
BBB wait begin time2 1540538529784
BBB wait end time 1540538529784
AAA wait end time 1540538529885

此时线程 BBB 在线程 AAA 被自动唤醒前就将线程 AAA 唤醒了,此时先执行完线程 BBB 的代码,在执行线程 AAA wait() 方法后面的代码

4.参考

《Java多线程编程核心技术》

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/83444613