首页 > 技术文章 > sleep、wait方法之间区别

yuanfeii 2021-06-02 17:48 原文

sleep、wait方法之间区别

1.所属的类不同

sleep是Thread类的静态方法,而wait是Object类的成员方法

2.锁机制不一样

  • sleep方法:会让出资源调度器为当前线程分配的时间片,也就是放弃cpu的使用权,但是sleep不会释放当前线程持有的锁资源。其缺点就是当其它线程想获得同样的锁资源会被阻塞。
  • wait方法:既放弃cpu使用权,也会释放锁资源。即不会阻塞其它线程

请看如下代码:创建两个线程t1、t2,t1获得锁后睡眠5s

@Slf4j
public class TestSleepAndWait {
    static final String lock = "abc";
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (lock){
                log.error("t1线程执行");
                try {
                    //
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1");

        Thread t2 = new Thread(()->{
            // t2获得锁后执行
            synchronized (lock){
                log.error("t2线程执行");
            }
        },"t1");

        t1.start();
        t2.start();
    }
}

执行结果:

16:51:01 [t1] gzhu.study.test.TestSleepAndWait - t1线程执行
16:51:06 [t1] gzhu.study.test.TestSleepAndWait - t2线程执行

由日志时间可以看到t2线程执行时间刚好在5s之后,说明t1睡眠过程中,t2一直在阻塞等待t1释放锁。

接下来我们尝试把sleep方法换成wait方法,上述其它代码不变

// t1线程等待
lock.wait();

查看运行结果:

16:57:55 [t1] gzhu.study.test.TestSleepAndWait - t1线程执行
16:57:55 [t1] gzhu.study.test.TestSleepAndWait - t2线程执行

可以看到t1、t2线程几乎在同一时间运行,说明wait方法使线程释放了说资源。值得注意的是,wait方法需要配合锁对象使用,即使用锁对象使当前线程阻塞。

3.调用之后的线程的状态不同

sleep方法调用后,线程进入TIME_WAITING状态,而wait方法调用后,线程会进入WAITING状态。

额外了解一下:什么是等待池?什么是锁池?

等待池就是:线程调用wait方法后,线程会进入一个LIFO(为什么是LIFO下面解释)等待被唤醒,在等待池的线程被唤醒之前是不会去竞争锁资源的。

锁池:存放竞争锁资源的线程,等待池的线程被notify()和notifyAll()方法唤醒之后,会先进入锁池而不是直接获得锁资源执行。另外有一个误区,笔者看到许多博客说sleep方法需要指定时间,而wait方法不需要指定时间其实是错的,如下:

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException 

第一种情况:

@Slf4j
public class TestSleepAndWait {
    static final String lock = "abc";
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (lock){
                log.error("t1线程执行");
                try {
                    // t1线程等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.error("t1线程继续执行");
            }
        },"t1");

        Thread t2 = new Thread(()->{
            // t2获得锁后执行
            synchronized (lock){
                log.error("t2线程执行");
            }
        },"t1");

        Thread t3 = new Thread(()->{
            // t2获得锁后执行
            synchronized (lock){
                log.error("t3线程执行");
            }
        },"t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

结果:

17:33:03 [t1] gzhu.study.test.TestSleepAndWait - t1线程执行
17:33:03 [t3] gzhu.study.test.TestSleepAndWait - t3线程执行
17:33:03 [t1] gzhu.study.test.TestSleepAndWait - t2线程执行

由于没有唤醒t1线程,即使t2、t3线程释放锁资源后,t1线程也没有继续执行。

第二种情况:

Thread t1 = new Thread(()->{
    synchronized (lock){
        log.error("t1线程执行");
        try {
            // t1线程等待
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.error("t1线程继续执行");
    }
},"t1");

Thread t2 = new Thread(()->{
    // t2获得锁后执行
    synchronized (lock){
        log.error("t2线程执行");
        lock.notify();
        while (true){

        }
    }
},"t1");

t1.start();
t2.start();

运行结果:

17:38:23 [t1] gzhu.study.test.TestSleepAndWait - t1线程执行
17:38:23 [t1] gzhu.study.test.TestSleepAndWait - t2线程执行

即使t1被t2唤醒进入锁池,但是由于t2一直循环未释放锁资源,使得t1也不能立即继续执行。

notify()方法与notifyAll()方法

notifAll方法,是将等待池中所有线程唤醒,并按照LIFO原则唤醒

notify方法是从等待队列中随机唤醒,如果只有一个线程则唤醒一个线程。

推荐阅读