一、sleep()介绍篇
铺垫:想要更好的了解sleep,要具备线程相关的知识
about1:线程的五大状态:
线程状态 | 状态解释 |
---|---|
新建状态(New) | 新创建了一个线程对象 |
就绪状态(Runnable) | 调用thread.start()方法后进入就绪状态。该状态的线程位于“可运行线程池”中,万事俱备,只欠CPU使用权。 |
运行状态(Running) | 就绪状态的线程获取了CPU,执行程序代码 |
死亡状态(Dead) | 线程执行完了或者因遇到error或exception退出了run()方法,该线程结束生命周期。 |
阻塞状态(Blocked) | 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。 |
about2:线程的五大状态之间的转换:
对线程的状态有了了解之后再来看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();
}
}
}
}