一、sleep()介绍篇

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

about1:线程的五大状态:

线程状态

状态解释

新建状态(New)

新创建了一个线程对象

就绪状态(Runnable)

调用thread.start()方法后进入就绪状态。该状态的线程位于“可运行线程池”中,万事俱备,只欠CPU使用权。

运行状态(Running)

就绪状态的线程获取了CPU,执行程序代码

死亡状态(Dead)

线程执行完了或者因遇到error或exception退出了run()方法,该线程结束生命周期。

阻塞状态(Blocked)

阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

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

show processlist sleep 的线程都是干嘛的 sleep后线程状态_jvm

对线程的状态有了了解之后再来看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();
            }
        }
    }
}