sleep()、wait()、notify()、notifyAll、线程五种状态之间的关系

一、sleep()介绍篇

铺垫:想要更好的了解sleep,要具备线程相关的知识

about1:线程的五大状态:

线程状态 状态解释
新建状态(New) 新创建了一个线程对象
就绪状态(Runnable) 调用thread.start()方法后进入就绪状态。该状态的线程位于“可运行线程池”中,万事俱备,只欠CPU使用权。
运行状态(Running) 就绪状态的线程获取了CPU,执行程序代码
死亡状态(Dead) 线程执行完了或者因遇到error或exception退出了run()方法,该线程结束生命周期。
阻塞状态(Blocked) 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

about2:线程的五大状态之间的转换:

image-20220311110245680.png

对线程的状态有了了解之后再来看sleep():

1、sleep()属于Thread类的方法。

2、可以让线程休眠定长时间,进入到线程阻塞状态。比如sleep(3000),执行后3s内线程处于阻塞状态。

3、sleep(3000)不代表着线程休眠3s后就会返回到运行状态,事实上是返回到就绪状态。就绪状态获取CPU使用权后才会进入到运行状态。

4、有锁时,sleep()不会释放锁。

针对第4点写个实例,解释下sleep()怎么又和锁扯上关系了呢。

package designmode.productconsumermode.waitandsleep;

import java.util.ArrayList;

public class SleepService {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Thread thread1 = new Thread(new Thread1(list));
        Thread thread2 = new Thread(new Thread2(list));
        thread1.start();
        thread2.start();
    }
}

class Thread1 implements Runnable{
    private ArrayList list;

    public Thread1(ArrayList list) {
        this.list = list;
    }

    @Override
    public void run() {
        synchronized (list) {
            try {
                System.out.println("线程:[" +Thread.currentThread().getName() + "]开启");
                System.out.println(Thread.currentThread().getName() + "sleep开始时间:" + System.currentTimeMillis()/1000);
                Thread.sleep(3*1000);
                System.out.println(Thread.currentThread().getName() +"sleep休眠结束时间" + System.currentTimeMillis()/1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 尝试在线程sleep的时候,启动另一个线程去访问list对象
 * 如果在线程sleep(3000)的3s内,控制台打印了该方法的输出语句,
 * 则证明sleep方法在休眠期间释放了对this对象的锁
 */
class Thread2 implements Runnable{
    private ArrayList list;

    public Thread2(ArrayList list) {
        this.list = list;
    }

    @Override
    public void run() {
        System.out.println("线程:[" +Thread.currentThread().getName() + "]开启");
        synchronized (list) {
            System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()/1000);
        }
    }
}






执行结果:
线程:[Thread-0]开启
Thread-0sleep开始时间:1646982984
线程:[Thread-1]开启
Thread-0sleep休眠结束时间1646982987
Thread-1:1646982987

分析执行结果:

Thread-0调用sleep(3000)方法休眠后,等了3s,Thread-1才进入到synchronized修饰的代码块中。

即:sleep后,只有等到休眠结束才会释放锁。

二、wait()介绍篇

知识铺垫:notify/notifyAll要了解一下:

  • obj.notify:随机唤醒对象obj的某个wait线程,使被唤醒的线程进入就绪状态

  • obj.notifyAll:唤醒对象obj的所有wait线程,使被唤醒的线程进入就绪状态

1、wait()是Object类的方法

2、wait()后,当前线程无限休眠,进入阻塞状态。直到被notify/notifyAll唤醒。(对应线程五大状态转换图中的:等待通知-收到通知示例)

3、wait(10*1000)后,当前线程休眠10s,进入阻塞状态。如果10s内被notify/notifyAll唤醒,则休眠结束,线程进入就绪状态;如果10s内没有被唤醒,则10s后自动结束休眠,进入就绪状态。

4、wait()会释放对象锁。以便于被别的线程拿到锁后,通过notify/notifyAll唤醒。

5、因为wait需释放锁,所以必须在synchronized中使用(没有锁怎么释放?没有锁时使用会抛出IllegalMonitorStateException(正在等待的对象没有锁))

针对第4点,同样实例走一走:

package designmode.productconsumermode.waitandsleep;

public class WaitService {
    public static void main(String[] args) throws InterruptedException {
        WaitService waitService = new WaitService();
        Thread thread1 = new Thread(new Thread11(waitService));
        Thread thread2 = new Thread(new Thread22(waitService));
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }

    public void mwait() {
        System.out.println("线程:[" +Thread.currentThread().getName() + "]开启");
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "开始wait:" + System.currentTimeMillis()/1000);
                // wait(2000)表示将锁释放2000毫秒,
                // 到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码。
                // 如果锁被其他线程占用,则等待其他线程释放锁。
                // 注意,设置了超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞;
                // 但是如果没设置超时时间的wait方法必须等待其他线程执行notify。
                this.wait(10*1000);
                System.out.println(Thread.currentThread().getName() + ":wait结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void doit() {
        System.out.println("线程:[" +Thread.currentThread().getName() + "]开启");
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + "执行notify");
            this.notify();
            System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()/1000);
        }

    }
}

class Thread11 implements Runnable{
    private WaitService waitService;

    public Thread11(WaitService waitService) {
        this.waitService = waitService;
    }

    @Override
    public void run() {
        waitService.mwait();
    }
}

class Thread22 implements Runnable{
    private WaitService waitService;

    public Thread22(WaitService waitService) {
        this.waitService = waitService;
    }

    @Override
    public void run() {
        waitService.doit();
    }
}
执行结果:
Thread-0开始wait:1646986113
线程:[Thread-1]开启
Thread-1执行notify
Thread-1:1646986114
Thread-0:wait结束

分析执行结果:

Thread-0开始wait后-->Thread-1执行notify-->Thread-0wait结束。

可以看出来,Thread-0中,执行完this.wait方法后,一定释放了this对象的锁。因为Thread-1获取了该对象的锁,才能通过this.notify()方法唤醒Thread-0,使其继续往下执行代码。

三、sleep()和wait()比较篇

相同点

都可以使当前线程休眠,让出CPU的使用权,进入阻塞状态

区别

1、sleep是属于Thread类中的方法;wait是属于Object类中的方法。

2、sleep方法有没有锁都可以调用;wait方法必须在有锁的情况下才能调用,否则会报错。即代码要在synchronized中。

3、sleep方法执行后,当前线程不会释放当前占据的对象锁;wait方法执行后会释放该对象的锁。

4、sleep是静态方法;wait是实例方法。

四、notify和notifyAll相关知识扩展

notes:

notify后不会立刻唤醒线程,而是等notify所在的synchronized(obj){}代码块执行完了,才会唤醒wait线程。

两个概念:

1、锁池:ThreadA中已经占据了obj对象的锁,此时ThreadB、ThreadC、ThreadD也需要obj的锁,那么ThreadB、ThreadC、ThreadD会进入obj对象的锁池中。

2、等待池:ThreadA中执行了obj.wait(),ThreadA会释放obj的锁,而后进入obj的等待池中。我的理解就是等待被唤醒(notify/notifyAll)的池子。

例如:ThreadA、ThreadB、ThreadC、ThreadD都执行了obj.wait,则此时obj的等待池中有(ThreadA、ThreadB、ThreadC、ThreadD),而obj.notify的作用就是随机唤醒obj等待池中的某个线程。

这两个概念理解起来也不难。

但是有个说法:notify/notifyAll唤醒线程也可以解释为是将线程由等待池移动到锁池。

这个我就不太理解了,线程A通过wait释放了锁-->被线程B的notify唤醒-->怎么A又要去竞争锁了呢?

我得猜想大概是这样的:

synchronized(obj){}给obj加锁,进入代码块-->obj.wait()释放锁-->被notify唤醒-->需要obj锁才能重新进入到wait所在的代码块中,继续执行wait下面的代码。

只有等synchronized(obj){}代码块中所有代码都执行完了,这个线程才会真正的释放掉锁,不再需要锁。

跑个实例试一试:这个实例可以证明notify是随即唤醒的

package designmode.productconsumermode.waitandsleep;

import java.util.concurrent.TimeUnit;

public class WaitAndNotify {
    public static void main(String[] args) {
        Object co = new Object();
        System.out.println(co);

        for (int i = 0; i < 5; i++) {
            MyThread t = new MyThread("Thread" + i, co);
            t.start();
        }

        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----Main Thread notify-----");
            for (int i = 0; i < 5; i++) {
                synchronized (co) {
                    co.notifyAll();
                }
            }
            // 唤醒线程后,继续抢占锁,如果wait的线程不再需要锁,那么wait后代码可以执行。
            // 否则的话就证明,还是需要wait过后还是需要拿到锁才能回到wait代码块中
            synchronized (co) {
                while (true) {}
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyThread extends Thread {
        private String name;
        private Object co;

        public MyThread(String name, Object o) {
            this.name = name;
            this.co = o;
        }

        @Override
        public void run() {
            System.out.println(name + " is waiting.");
            try {
                synchronized (co) {
                    co.wait();
                }
                System.out.println(name + " has been notified.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354