天天看点

java基础之线程等待唤醒机制(适合小白)

今天我们来聊聊线程中的等待唤醒机制。

java中线程有以下六种状态:新建状态,阻塞状态,运行状态,死亡状态,休眠状态以及无限等待状态。

当我们手动new一个Thread或者其子类的时候,此时线程就处于新建状态(NEW)。调用start方法,如果CPU此时处于空闲状态(该线程抢夺到CPU执行时间),则该线程进入运行状态(RUNNABLE),否则进入阻塞状态(BLOCKED)准备抢夺下一次CPU执行时间。当该线程运行完毕或者调用stop方法结束该线程或者运行过程中抛出异常,则进入死亡状态(TERMINATED)。只有调用sleep(time)或者wait(time)方法后,线程才会进入休眠状态(TIMED_WAITING)。当计时结束时,如果cpu空间,则该线程进入运行状态,否则进入阻塞状态。如果使用sleep()方法或者wait()方法(这里两个方法相比于前文少了时间参数),则该线程会进入永久等待状态,直至调用notify方法唤醒。

休眠状态/永久休眠状态相比阻塞状态的区别:

休眠状态和永久休眠状态都是主动放弃CPU资源,即使CPU处于空闲状态,该线程仍然不会执行。

而阻塞状态并没有放弃CPU执行资格,一旦CPU空闲便会被执行。

java基础之线程等待唤醒机制(适合小白)

锁阻塞:新创建的线程处于可运行状态(需要获得CPU执行时间),如果未能争取到锁对象,则进入锁阻塞状态,否则进入运行状态。

接下来我们详细了解下无限等待状态。

先给出等待唤醒的案例场景以便于理解。

现在有个饭馆卖饭的场景,饭店的厨师和来往的客人分别是生产者和消费者。来一个客人,点菜完毕后,需要等待厨师制作菜肴才能开吃。客人点菜后的等待可以看成无限等待状态,直到厨师菜肴制作完毕后通知他,他才会被唤醒。

现在上代码:

java基础之线程等待唤醒机制(适合小白)

这里创建了两个匿名内部类分别模拟生产者和消费者。客人点菜后需要等待厨师制作,这里我们设定了五秒的时间来模拟。需要注意的是,

obj.wait()

obj.notify()两句代码。

这里不论是睡眠还是唤醒,他们都需要使用同一个锁对象来执行!

运行结果:

java基础之线程等待唤醒机制(适合小白)

等待五秒后...

java基础之线程等待唤醒机制(适合小白)

这里生产者(厨师)和消费者(客人)分别是两个线程。

客人要吃饭(消费者需要资源),通知厨师做饭(唤醒生产者线程),厨师做饭,消费者进入等待状态。等厨师做饭完毕,会告诉消费者饭已经做好了(唤醒消费者线程),消费者吃饭(消费者线程被唤醒),厨师等待(生产者线程进入等待状态)。等消费者吃饭完毕,会告诉厨师我吃完了,还要继续吃,消费者进入等待状态,厨师线程被唤醒......

wait():线程不再活动,不再参与调度,进入到wait set中,所以此时此线程不会浪费cpu资源,也不会去竞争cpu资源。这时候该线程状态就是WAITING。只有别的线程执行一个动作——通知(notify)在这个对象上等待的线程从wait set中释放出来,重新进入调度队列。

notify:选取所通知的对象的wait set中的一个线程释放。规则是:等待时间最长的线程优先被释放。

notifyAll():释放所通知对象上的全部线程到调度队列。

注意:哪怕只通知了一个线程,被通知的线程也不会立刻恢复执行状态,因为它当初中断的地方是在同步块内,此时它并没有持有锁。所以他会和其他线程一同竞争cpu资源。只有它竞争成功才能恢复执行。

下面给个例子模拟下线程的等待唤醒场景。

现在有个餐馆,有客人去吃饭。我们需要三个类:客人(消费者),厨师(生产者)以及菜(公共资源)

首先是菜类:

java基础之线程等待唤醒机制(适合小白)

餐厅类:

java基础之线程等待唤醒机制(适合小白)

消费者类:

java基础之线程等待唤醒机制(适合小白)

这里为了保证消费者和生产者的互斥性(消费者和生产者同一时刻只能有一人占有foot资源),必须使用同步技术。这里使用synchronize同步代码块。

默认资源最初时为无(flag = false),消费者占有资源时发现资源不存在,自身进入等待状态,调用wait方法。此时生产者获得资源,发现资源不存在,生产资源。当资源生产完毕,则通知消费者线程(notify)。消费者被唤醒后,消费资源,等资源消费完毕,唤醒生产者资源,如此反复循环。

main方法如下:

java基础之线程等待唤醒机制(适合小白)

运行结果如下:

java基础之线程等待唤醒机制(适合小白)