sleep() 和 wait() 的区别,以及 notify() 和 notifyall() 的区别

张贤 2020年03月13日 89次浏览

sleep() 和 wait() 的区别

  • sleep() 是 Thread 类的方法,wait() 是 Object 类的方法
  • sleep() 方法可以在任何地方使用。wait() 方法只能在 synchronized 方法或者 synchronized 块中使用
  • 最主要的本质区别:Thread.sleep() 只会让出 CPU,不会使得当前线程释放锁,不会影响锁的相关行为。Object.wait() 不仅使得当前线程会让出 CPU,还会释放已经占用的同步资源锁。如下代码:
public class WaitSleepDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread A is waiting to get lock");
                synchronized (lock) {
                    System.out.println("Thread A get lock");
                    try {
                        Thread.sleep(20);
                        System.out.println("Thread A do wait method");
                        //线程 A 释放锁,此时线程 B 可以拿到锁,进入 synchronized 代码块
                        lock.wait(1000);
                        //这一句代码会阻塞,直到线程 B 完成后会释放锁,此时线程 A 拿到锁,执行这一句
                        System.out.println("Thread A is done");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        //睡眠 20ms 后,在启动 B 线程,此时线程 A 已经获取了锁
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread B is waiting to get lock");
                //线程 B 阻塞在这里,直到线程 A 执行 lock.wait(1000); 时,线程 B 才继续往下执行
                synchronized (lock) {

                    System.out.println("Thread B get lock");
                    try {
                        System.out.println("Thread B is sleep");
                        Thread.sleep(10);
                        System.out.println("Thread B is Done");

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

输出是:

Thread A is waiting to get lock
Thread A get lock
Thread B is waiting to get lock
Thread A do wait method
Thread B get lock
Thread B is sleep
Thread B is Done

注意在线程 A 中,使用了带有 Timeout 参数的lock.wait(1000);。如果这里不带 Timeout 参数,则线程 A 会永远处于等待和阻塞中,不会拿到锁,唯一的办法是在线程 B 中使用lock.notify()或者lock.notifyAll()去显示唤醒线程 A。

notify()notifyall()的区别

在 Java 中有等待池和锁池,和notify()以及notifyall()关系紧密,我们需要先了解等待池和锁池的概念

  • 等待池
    假设线程 A 调用了某个对象的wait()方法,线程 A 就进入了该对象的等待池中,进入到等待池中的线程不会去竞争

  • 锁池
    假设线程 A 已经拥有了某个对象(不是类)的锁,而其他线程 B、C 想要调用这个对象的某个synchronized方法或者方法块,由于 B、C 线程在进入对象的synchronized方法或者方法块之前必须先获得该对象的拥有权,而恰巧该对象目前正在被线程 A 所占用,此时 B、C 线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

  • notifyAll() 会让所有处于等待等待池的线程全部进入锁池去竞争获取锁的机会

  • notify() 只会随机选取一个处于等待池中的线程进去锁池去竞争获取锁的机会

下面是一个例子

public class NotificationDemo {
    private volatile boolean go = false;

    public static void main(String[] args) {
        final NotificationDemo test = new NotificationDemo();

        Runnable waitTask = new Runnable() {
            @Override
            public void run() {
                try {
                    test.shouldGo();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " finished Execution");
            }
        };

        Runnable notifyTask = new Runnable() {
            @Override
            public void run() {
                test.go();
                System.out.println(Thread.currentThread().getName() + " finished Execution");
            }
        };

        Thread t1 = new Thread(waitTask, "wait task 1");//will wait
        Thread t2 = new Thread(waitTask, "wait task 2");//will wait
        Thread t3 = new Thread(waitTask, "wait task 3");//will wait
        Thread t4 = new Thread(notifyTask, "notify task 1");//will notify

        //start waiting thread
        t1.start();
        t2.start();
        t3.start();
        //pause to ensure all thread start successfully
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //starting notify thread
        t4.start();
    }

    //both shouldGo() and go() are locked on current object
    private synchronized void shouldGo() throws InterruptedException {
        while (go != true) {
            System.out.println(Thread.currentThread().getName() + " is going to wait on this object");
            wait();//release lock and reacquires on wakeup
            System.out.println(Thread.currentThread().getName() + " is woken up");
        }
        go = false;//resetting condition
    }

    //both shouldGo() and go() are locked on current object
    private synchronized void go() {
        while (go != true) {
            System.out.println(Thread.currentThread().getName() + " is going to notify");
            go = true;//making condition true for waiting thread
            //notify();//notify the one of the three threads waiting for lock
            notifyAll();
        }
    }
}

代码中需要注意的是:

  • go变量是多个线程都需要访问的变量,设置为volatile类型的,可以使得go变量在被一个修改后,另一个线程能够立刻读取到最新的go变量
  • go()shouldGo()方法都是synchronized的,代表同一时刻只有一个线程能运行
  • t1、t2、t3 三个线程依次在go()方法中都会调用wait()方法释放锁并阻塞,然后 t4 线程会在shouldGo()方法中调用notify()方法随机唤醒其中的一个线程放入锁池,由于此时锁池中只有一个线程在竞争锁,所以该线程肯定会获得锁。因此代码输出如下:
wait task 1 is going to wait on this object
wait task 3 is going to wait on this object
wait task 2 is going to wait on this object
notify task 1 is going to notify
wait task 1 is woken up
notify task 1 finished Execution
wait task 1 finished Execution
  • notifyAll()方法会把等待池中的所有线程都唤醒放入锁池中去争夺锁,在同一时刻只有一个线程可以获得锁。如果把shouldGo()中的notify()改为notifyAll(),三个线程会依次获得锁完成执行。输出如下:
wait task 1 is going to wait on this object
wait task 2 is going to wait on this object
wait task 3 is going to wait on this object
notify task 1 is going to notify
notify task 1 finished Execution
wait task 3 is woken up
wait task 2 is woken up
wait task 2 is going to wait on this object
wait task 3 finished Execution
wait task 1 is woken up
wait task 1 is going to wait on this object