第9章 线程协作-wait¬ify
1.1 wait¬ify实现线程协作
1.1.1 线程协作案例需求分析
在多线程开发中为了充分利用系统资源,将一个任务拆分为多个小任务,这样的话一个任务需要多线程协作来完成任务,比如线程A要处理的数据由线程B来提供;线程A处理完成去通知 线程B继续处理,线程与线程之间是协同完成任务而不是独立完成任务。
预见猿份视频处理程序由多线程协作完成视频处理,一个原始视频需要经过转码和切割,第一步先将视频统一转成mp4格式,第二步再将mp4视频切割成m3u8格式,一部分线程负责完成第一步,一部分线程负责完成第二步。
为了便于学习暂时将上边的需求简化为两个线程之间的协作:线程A完成视频 转mp4,线程B完成视频转m3u8,线程B需要等线程A先完成转mp4的处理然后将mp4转成m3u8,最终完成一个视频的处理。
如下图:

需求如下:
1、线程A负责转MP4,线程B负责转m3u8。
2、线程A先工作,线程B后工作。
3、线程A完成任务需要通知线程B去工作。
分析如下:
1、线程A和线程B的工作有先后
线程A负责转MP4并且先去执行任务,线程A完成任务后由线程B开始执行转M3u8任务,如果线程A和线程B同时启动,则可以通过一个共享变量控制视频的工作状态,比如设置如下共享变量:
mp4完成状态:false 表示未完成mp4转换,true表示完成mp4转换。
2、线程A完成任务需要通知线程B
根据当前所学知识线程之间的通信方法如下:
1)中断请求
线程A向线程B发起中断请求,线程A需要通过Thread对象向线程B发起中断请求,此方法的问题在于线程A和线程B的交互存在耦合,不易扩展,另外对于使用线程池来说需要从池子里拿到线程B的Thread对象,过程略显复杂。
2)阻塞队列
线程A将视频完成视频放入阻塞队列,线程B从阻塞队列获取视频进行处理,阻塞队列将线程A和线程B解耦合,阻塞队列在多线程通信中使用广泛,但是针对本需求线程A通过队列通知线程B有些笨重。
3)共享变量
可以设置一个共享变量存储视频转换状态,线程A完成转MP4工作后设置工作状态,线程B读取工作状态可知线程A是否完成工作。此方法较简单,本次测试使用此方法。
1.1.2 使用共享变量实现线程协作
根据需求分析下边通过共享变量完成线程之间的协作。
代码如下:
1、首先创建视频类
这里创建一个内部类,类中设置两个状态变量:mp4完成状态、视频转换是否结束。
mp4完成状态:false 表示未完成mp4转换,true表示完成mp4转换。
视频转换是否结束:false表示视频转换未完成,true表示视频转换完成。
package com.yjoffer.javase.thread.waitnotify;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//视频类
public class PbVideo{
// 视频名称
String name;
// mp4完成状态
boolean mp4finished = false;
//m3u8完成状态
boolean m3u8finished = false;
//视频转换结束
boolean finished = false;
public PbVideo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMp4finished() {
return mp4finished;
}
public void setMp4finished(boolean mp4finished) {
this.mp4finished = mp4finished;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public boolean isM3u8finished() {
return m3u8finished;
}
public void setM3u8finished(boolean m3u8finished) {
this.m3u8finished = m3u8finished;
}
}创建两个任务类,一个是转MP4、一个是转m3u8。
/**
* 转mp4
*/
class Mp4RunnableV1 implements Runnable{
//视频
private PbVideo pbVideo;
public Mp4RunnableV1(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
if(pbVideo.isMp4finished()){
return ;
}
logger.debug("开始转mp4...");
try {
//模拟视频转换过程
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setMp4finished(true);
logger.debug("转mp4结束! ");
}
}
/**
* 转M3u8
*/
class M3u8RunnableV1 implements Runnable{
private PbVideo pbVideo;
public M3u8RunnableV1(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
if(pbVideo.isM3u8finished()){
return ;
}
while (!pbVideo.isMp4finished()){
logger.debug("M3u8Runnable暂时休息");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.debug("开始转m3u8...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setM3u8finished(true);
pbVideo.setFinished(true);
logger.debug("转m3u8结束! ");
}
}最后编写测试方法:
/**
* 线程协作测试
* @author 预见猿份(www.yjoffer.com)
*
*/
public class ThreadWaitnotifyTest {
private static Logger logger = Logger.getLogger(ThreadWaitnotifyTest.class);
...
public static void test_videoconvert1(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//视频对象
PbVideo pbVideo = new PbVideo("预见猿份Java自学教程");
//启动一个线程开始转mp4
threadPool.execute(new Mp4RunnableV1(pbVideo));
//启动一个线程开始转m3u8
threadPool.execute(new M3u8RunnableV1(pbVideo));
shutdown(threadPool);
}
....编写 main方法运行测试方法test_videoconvert(),输出 如下:
10:52:25.234 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 开始转mp4...
10:52:25.235 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:52:26.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:52:27.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:52:28.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:52:28.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 转mp4结束!
10:52:29.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 开始转m3u8...
10:52:32.256 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 转m3u8结束!从日志可以看出符合预期,虽然结果正确,但是方案1存在问题:
1、M3u8Runnable线程不能及时得到通知。
M3u8Runnable线程检查MP4转换未完成则休眠数秒,如果正在休眠时完成了MP4转换这时是得不到通知的,需要等休眠时间到下一轮循环方可得到通知。
有人说可以将休眠时间设置小一点就可以相对及时得到通知,此时又会存在另一个问题就是:线程B在没有工作时会频繁占用CPU。
1.1.3 wait¬ify工作流程
基于方案1存在问题,我们思考如何及时收到通知还不用频繁占用CPU。
首先使用一个同步锁让线程A和线程B去竞争,保证两者只能有一个去工作。
如果是线程A先抢到锁正符合要求,线程A完成任务释放锁,然后线程B拿到锁去工作。如下图:

如果是线程B先抢到了锁,此时线程B需要让出锁进入等待队列,线程A拿到锁去工作,完成工作后唤醒线程B。如下图:

线程B如何释放锁进入等待队列?线程A如何唤醒等待中的线程,具体的工作流程如下:
1、线程A和线程B争抢锁,线程B抢到锁,线程A在监视器的阻塞队列中,如下图:

2、线程B调用wait方法释放锁并进入等待队列,由于锁被释放线程A获取锁开始执行,如下图:

3、线程A处理完成调用notify方法,通知监视器的中正在等待的一个线程(注意只会通知到一个线程),线程A结束,此时等待队列 中只有线程B所以线程B收到通知并进入阻塞队列,如下图:

4、线程B争抢锁,抢到锁开始执行,如下图:

1.1.4 wait¬ify入门程序
根据上边的分析的wait¬ify的工作流程,编写 入门程序如下:
//wait/notify入门程序
public static void test_waitnotify_first(){
Object obj = new Object();
new Thread(()->{
synchronized (obj){
logger.debug("拿到锁");
try {
logger.debug("进入等待队列");
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.debug("被唤醒");
}
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
synchronized (obj){
logger.debug("拿到锁");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
obj.notify();
logger.debug("去唤醒等待的线程");
}
},"B").start();
}运行:
13:13:40.393 FINE com.yjoffer.javase.thread2.waitnotify.ThreadWaitnotifyTest[A] - 拿到锁
13:13:40.417 FINE com.yjoffer.javase.thread2.waitnotify.ThreadWaitnotifyTest[A] - 进入等待队列
13:13:41.393 FINE com.yjoffer.javase.thread2.waitnotify.ThreadWaitnotifyTest[B] - 拿到锁
13:13:43.393 FINE com.yjoffer.javase.thread2.waitnotify.ThreadWaitnotifyTest[B] - 去唤醒等待的线程
13:13:43.393 FINE com.yjoffer.javase.thread2.waitnotify.ThreadWaitnotifyTest[A] - 被唤醒wait/notify工作流程需要注意以下几点:
1、wait/notify都是Object类的方法。
2、等待队列与阻塞队列一样都与监视器关联,监视器与锁对象关联,所以可以认为等待队列与阻塞队列与锁对象关联。
3、线程只有获得锁才可以调用锁对象的wait方法,调用此方法表示自己释放锁并进入等待队列。
4、线程只有获得锁才可以调用锁对象的notify方法,调用此方法表示通知该锁对象关联的等待队列中一个线程(注意是通知其中一个线程),线程收到通知进入阻塞队列等待竞争锁,当竞争到锁后从当初调用wait方法处继续执行。
1.1.5 wait¬ify实现线程协作案例
根据wait/notify的工作流程,下边使用wait/notify实现本节的案例,代码如下:
重写两个任务类,一个是转MP4、一个是转m3u8。
/**
* 转mp4版本2,用wait/notify实现
*/
class Mp4RunnableV2 implements Runnable{
//视频
private PbVideo pbVideo;
public Mp4RunnableV2(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isMp4finished()){
return ;
}
logger.debug("开始转mp4...");
try {
//模拟视频转换过程
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setMp4finished(true);
logger.debug("转mp4结束! ");
pbVideo.notify();
}
}
}
/**
* 转M3u8版本2,用wait/notify实现
*/
class M3u8RunnableV2 implements Runnable{
private PbVideo pbVideo;
public M3u8RunnableV2(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isM3u8finished()){
return ;
}
while (!pbVideo.isMp4finished()){
logger.debug("M3u8Runnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.debug("开始转m3u8...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setFinished(true);
logger.debug("转m3u8结束! ");
}
}
}测试:
public static void test_videoconvert2(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//视频对象
PbVideo pbVideo = new PbVideo("预见猿份Java自学教程");
//启动一个线程开始转mp4
threadPool.execute(new Mp4RunnableV2(pbVideo));
//启动一个线程开始转m3u8
threadPool.execute(new M3u8RunnableV2(pbVideo));
shutdown(threadPool);
}运行程序,输出:
19:26:42.558 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 开始转mp4...
19:26:45.579 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 转mp4结束!
19:26:45.579 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 开始转m3u8...
19:26:48.580 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 转m3u8结束!1.1.6 wait与sleep的区别
下面总结 wait与sleep的区别:
1)wait和notify是Object类的方法,sleep是Thread类的方法。
2)sleep在任意地方都可以执行,wait和notify只能在synchronized块中执行,这表示只是获取锁后方可执行。
3)调用wait后当前线程释放锁,如果在synchronized块中调用sleep是不会释放锁的。
4)sleep方法不需要被唤醒,休眠时间到自动唤醒,wait()方法需要被唤醒,其它线程调用notify/notifyAll唤醒正在wait的线程。
注意:wait()方法还有一个重载方法wait(long timeout),它可以指定一个超时时间,到达超时时间自动从等待队列到阻塞队列准备竞争锁,此时它不需要其它线程调用notify/notifyAll。
1.1.7 升级线程协作需求
现在此案例的需求需要升级,如下:
1、增加视频加密功能。
2、视频切割后即转m3u8后进行视频加密。
根据升级的需求进行分析:
1、增加一个视频加密的线程。
2、三个线程的关系是:转mp4线程先工作,完成后通知转m3u8线程,m3u8线程完成后通知视频加密线程。
代码如下:
定义视频类,在原有基础上增加视频加密状态。
package com.yjoffer.javase.thread.waitnotify;import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
- 线程协作测试
- @author 预见猿份(www.yjoffer.com)
*/
public class PbVideo {
// 视频名称
String name;
// mp4完成状态
boolean mp4finished = false;
//m3u8完成状态
boolean m3u8finished = false;
//视频转换结束
boolean finished = false;
//加密状态
boolean encrypted = false;
public PbVideo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMp4finished() {
return mp4finished;
}
public void setMp4finished(boolean mp4finished) {
this.mp4finished = mp4finished;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public boolean isM3u8finished() {
return m3u8finished;
}
public void setM3u8finished(boolean m3u8finished) { this.m3u8finished = m3u8finished;
}
public boolean isEncrypted() {
return encrypted;
}
public void setEncrypted(boolean encrypted) {
this.encrypted = encrypted;
}
}</code></pre>定义三个任务类:
/**
* 转mp4版本3,用wait/notify实现
*/
class Mp4RunnableV3 implements Runnable{
//视频
private PbVideo pbVideo;
public Mp4RunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isMp4finished()){
return ;
}
logger.debug("开始转mp4...");
try {
//模拟视频转换过程
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setMp4finished(true);
logger.debug("转mp4结束! ");
//通知后续工作线程
pbVideo.notify();
}
}
}
/**
* 转M3u8版本3,用wait/notify实现
*/
class M3u8RunnableV3 implements Runnable{
private PbVideo pbVideo;
public M3u8RunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isM3u8finished()){
return ;
}
while (!pbVideo.isMp4finished()){
logger.debug("M3u8Runnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(pbVideo.isMp4finished() && !pbVideo.isM3u8finished()){
logger.debug("开始转m3u8...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setM3u8finished(true);
logger.debug("转m3u8结束! ");
//通知后续工作线程
pbVideo.notify();
}else{
logger.debug("未达到转m3u8条件");
}
}
}
}
/**
* 视频加密版本3,用wait/notify实现
*/
class EncRunnableV3 implements Runnable{
private PbVideo pbVideo;
public EncRunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isEncrypted()){
return ;
}
if (!pbVideo.isM3u8finished()){
logger.debug("EncRunnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(pbVideo.isM3u8finished() && !pbVideo.isEncrypted()){
logger.debug("开始加密...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setEncrypted(true);
pbVideo.setFinished(true);
logger.debug("加密结束! ");
}else{
logger.debug("未达到加密条件");
}
}
}
}测试代码:
注意:先启动加密线程进行测试。
public static void test_videoconvert3(){
ExecutorService threadPool = Executors.newCachedThreadPool();
//视频对象
PbVideo pbVideo = new PbVideo("预见猿份Java自学教程");
//视频加密
threadPool.execute(new EncRunnableV3(pbVideo));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//转mp4
threadPool.execute(new Mp4RunnableV3(pbVideo));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//转m3u8
threadPool.execute(new M3u8RunnableV3(pbVideo));
shutdown(threadPool);
}运行程序,输出 :
09:58:04.864 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - EncRunnable暂时休息
09:58:04.893 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
09:58:05.866 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 开始转mp4...
09:58:08.866 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 转mp4结束!
09:58:08.866 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 没到加密条件!从日志可以看出执行流程如下:
1、首先启动加密线程和m3u8线程,由于工作条件未达到两个线程都进入等待队列。
2、1秒后启动mp4线程,正常执行,转mp4结束后调用notify方法通知后续线程,结果加密线程收到通知。
3、由于加密线程的工作条件未达到,最终结束运行。
4、程序运行到最后m3u8线程还在等待队列等待通知。
此程序的问题有两个:
1、加密线程收到notify通知发现工作条件未达到却结束了线程,正确作法应该继续等待。
2、mp4线程完成工作后通知后续线程应该要通知到m3u8线程,结果却通知了加密线程。
1.1.8 等待重试功能
解决第一个问题很简单只需要将if改为while即可,代码如下:
//如果工作条件没有达到继续进入下一次循环继续等待,直到工作条件达到为止
while (!pbVideo.isM3u8finished()){
logger.debug("EncRunnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}运行程序,输出 如下:
10:10:31.882 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - EncRunnable暂时休息
10:10:31.903 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:10:32.882 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 开始转mp4...
10:10:35.883 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 转mp4结束!
10:10:35.883 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - EncRunnable暂时休息虽然程序最终结果还不正确,但是从日志可以看出加密线程的工作条件未达到时又一次开始休息,这是符合我们的预期的。
所以在使用wait/notify时注意工作条件的判断使用while,模板代码如下:
synchronized(obj){
while(工作条件未达到){
obj.wait();
}
//开始工作
}1.1.9 解决虚假唤醒问题
为什么会出现问题2呢?
因为notify方法会通知等待队列中的任意一个线程,由于这个是随机的所以mp4线程之后应该是m3u8线程去工作结果却通知到其它线程,这个现象叫虚假唤醒。
下图还原了程序的执行过程:
1)最开始m3u8线程和加密线程在等待队列

2)虚假唤醒后见下图
此时m3u8线程还在等待队列,加密线程在阻塞队列 等待获取锁。

解决虚假唤醒要使用notifyAll方法,此方法会通知等待队列中的所有线程,见下图:
mp4线程调用notifyAll方法后等待队列 中的所有线程到阻塞队列竞争获取锁。
即使是加密线程得到锁,它判断工作条件未达到又会进入等待队列同时释放锁 ,此时m3u8线程就会得到锁,m3u8线程完成工作后再次调用notifyAll,此时加密线程进入阻塞队列,最后获取锁进行视频加密。

所以,在使用wait/notify时除了注意判断工作条件的方法以外还要注意虚假唤醒问题,总结模板代码如下:
synchronized(obj){
while(工作条件未达到){
obj.wait();
}
//开始工作
obj.notifyAll();
}
//另一个线程
synchronized(obj){
while(工作条件未达到){
obj.wait();
}
//开始工作
obj.notifyAll();
}根据上边的分析修改后的完整代码如下:
/**
* 转mp4版本3,用wait/notify实现
*/
class Mp4RunnableV3 implements Runnable{
//视频
private PbVideo pbVideo;
public Mp4RunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isMp4finished()){
return ;
}
logger.debug("开始转mp4...");
try {
//模拟视频转换过程
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setMp4finished(true);
logger.debug("转mp4结束! ");
//通知后续工作线程
pbVideo.notifyAll();
}
}
}
/**
* 转M3u8版本3,用wait/notify实现
*/
class M3u8RunnableV3 implements Runnable{
private PbVideo pbVideo;
public M3u8RunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isM3u8finished()){
return ;
}
while (!pbVideo.isMp4finished()){
logger.debug("M3u8Runnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(pbVideo.isMp4finished() && !pbVideo.isM3u8finished()){
logger.debug("开始转m3u8...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setM3u8finished(true);
logger.debug("转m3u8结束! ");
//通知后续工作线程
pbVideo.notifyAll();
}
}
}
}
/**
* 视频加密版本3,用wait/notify实现
*/
class EncRunnableV3 implements Runnable{
private PbVideo pbVideo;
public EncRunnableV3(PbVideo pbVideo){
this.pbVideo = pbVideo;
}
public void run() {
synchronized (pbVideo){
if(pbVideo.isEncrypted()){
return ;
}
//如果工作条件没有达到继续进入下一次循环继续等待,直到工作条件达到为止
while (!pbVideo.isM3u8finished()){
logger.debug("EncRunnable暂时休息");
try {
pbVideo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(pbVideo.isM3u8finished() && !pbVideo.isEncrypted()){
logger.debug("开始加密...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
pbVideo.setEncrypted(true);
pbVideo.setFinished(true);
logger.debug("加密结束! ");
}
}
}
}重新运行测试程序,输出 如下:
10:27:35.772 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - EncRunnable暂时休息
10:27:35.791 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - M3u8Runnable暂时休息
10:27:36.773 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 开始转mp4...
10:27:39.773 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-3] - 转mp4结束!
10:27:39.773 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 开始转m3u8...
10:27:42.774 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-2] - 转m3u8结束!
10:27:42.774 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 开始加密...
10:27:45.774 FINE com.yjoffer.javase.thread.waitnotify.ThreadWaitnotifyTest[pool-1-thread-1] - 加密结束!程序结果符合预期。
请免费关注 预见猿份 公众号,发送:yjoffer.com,获取解锁密码
微信扫码关注关注公众号回复获取密码后,在下方输入并点击解锁,本机后续再读无需重复输入。
