文章目录
1 场景描述
虽然Thread为我们提供了可获取状态,以及判断是否alive的方法,但是这些方法均是针对线程本身的,而我们提交的任务Runnable在运行过程中所处的状态如何是无法直接获得的,比如它什么时候开始,什么时候结束,最不好的一种体验是无法获得Runnable任务执行后的结果。一般情况下想要获得最终结果,我们不得不为Thread或者Runnable传入共享变量,但是在多线程的情况下,共享变量将导致资源的竞争从而增加了数据不一致性的安全隐患。
2 观察者模式
被观察者发生变化的时候,通知观察者,观察者就监听到这一变化可做成相应的反应
当某个对象发生状态改变需要通知第三方的时候,观察者模式就特别适合胜任这样的工作。观察者模式需要有事件源,也就是引发状态改变的源头,很明显Thread负责执行任务的逻辑单元,它最清楚整个过程的始末周期,而事件的接收者则是通知接受者一方,严格意义上的观察者模式是需要Observer(观察者)的集合的,我们在这里不需要完全遵守这样的规则,只需将执行任务的每一个阶段都通知给观察者即可
核心角色:
- 被观察者:Observerable/Subject
- 观察者:Observer
3 实现
3.1 接口定义
3.1.1 Observable接口定义(被观察者)
Observable接口:可被观察,该接口主要是暴露给调用者使用的,其中四个枚举类型分别代表了当前任务执行生命周期的各个阶段
观察者模式的核心角色,被观察对象需要实现该接口,这个场景下,我们的线程是我们观察的目标,所以需要实现该接口。
public interface Observable {
/**
* 任务生命周期的枚举类型
*/
enum Cycle{
STARTED, RUNNING, DONE, ERROR
}
/**
* 获取当前任务的生命周期状态
* @return
*/
Cycle getCycle();
/**
* 可通过Observable的start对线程进行启动
* 定义启动线程的方法,主要作用是为了屏蔽Thread的其他方法
* 提现面向对象的封装性
*/
void start();
/**
* 可通过Observable的interrupt对当前线程进行中断
* 定义线程的打断方法,作用与start方法一样,也是为了屏蔽Thread的其他方法
*/
void interrupt();
}
3.1.2 TaskLifecycle接口定义(观察者)
这个接口就是定义,线程开始执行,运行中,执行结束,执行出现异常的回调接口
public interface TaskLifecycle<T> {
/**
* 任务启动时候触发的方法
* @param thread
*/
void onStart(Thread thread);
/**
* 任务正在运行时候会触发的方法
* 由于我们针对的是任务的生命周期,不同于线程生命周期中的RUNNING状态,
* 如果当前线程进入了休眠或者阻塞,那么任务都是running状态。
* @param thread
*/
void onRunning(Thread thread);
/**
* 任务运行结束时,会触发发的结果
* @param thread
* @param result result是任务执行结束后的结果
*/
void onFinish(Thread thread,T result);
/**
* 任务执行报错时会触发onError方法
* @param thread
* @param e 发生的异常
*/
void onError(Thread thread, Exception e);
/**
* 空实现
* @param <T>
*/
class EmptyLifecycle<T> implements TaskLifecycle<T> {
@Override
public void onStart(Thread thread) {
//do nothing
}
@Override
public void onRunning(Thread thread) {
//do nothing
}
@Override
public void onFinish(Thread thread, T result) {
//do nothing
}
@Override
public void onError(Thread thread, Exception e) {
//do nothing
}
}
}
在内部提供了一个空实现,这样使用者如果不自己实现,默认就是这个空实现,使用者并不会感知到其存在,就可以保持对Thread类的使用习惯
3.1.3 Task函数接口定义
该接口替代jdk原生Runnable接口,由于我们需要对线程中的任务执行增加可观察的能力,并且需要获得最后的运行结果,因此Runnable接口在可观察的线程中将不再使用,取而代之的是Task接口,其作用与Runnable类似,主要用于承载任务的逻辑执行单元。
@FunctionalInterface
public interface Task<T> {
/**
* 任务执行接口,返回任务结果, 替代的就是Runnable接口的run方法,
* 区别就是这个方法支持返回值
* @return
*/
T call();
}
3.2 接口实现
3.2.1 ObservableThread实现
因为Thread类是jdk,我们不可能让他去实现Observable接口,所以只好自己定义一个类,来实现Observable接口 ,
但是又需要它具有Thread的作用,所以ObservableThread还需要继承Thread。
并且需要重写run方法,将其修饰为final类型,不允许子类再次对其进行重写,重写主要是为了能够让其具有监控能力,也就是能够在每个节点通知到我们的观察者(TaskLifecycle接口的实现)
并且在线程执行的过程中需要通知观察者,所以观察者(ObservableThread)需要持有被观察者(TaskLifecycle接口的实现)
/**
* @author wyaoyao
* @date 2021/4/5 11:56
* 可观察的线程
* 要具备两个能力:
* 1. 得是线程:所以要继承Thread
* 2. 要可观察,提供观察能力,所以需要实现Observable接口
*/
public class ObservableThread<T> extends Thread implements Observable {
private final TaskLifecycle<T> lifecycle;
/**
* 任务,可理解为Runnable接口
*/
private final Task<T> task;
/**
* 当前线程执行到的节点
*/
private Cycle cycle;
public ObservableThread(TaskLifecycle<T> lifecycle, Task<T> task) {
if (Objects.isNull(task)) {
// task 不能是空
throw new IllegalArgumentException("The task is required but now is null");
}
this.lifecycle = lifecycle;
this.task = task;
}
public ObservableThread(Task<T> task) {
// 指定Task的实现,默认情况下使用EmptyLifecycle
this(new TaskLifecycle.EmptyLifecycle<>(), task);
}
/**
* 重写Thread的run方法
* 使用final修饰,不能被重写
*/
@Override
public final void run() {
// 任务执行的时候,更新状态为开始
this.update(Cycle.STARTED, null, null);
try {
// 开始执行任务
this.update(Cycle.RUNNING, null, null);
// 执行任务
T result = task.call();
// 执行结束
this.update(Cycle.DONE, result, null);
} catch (Exception e) {
// 如果出现异常,执行异常回调
this.update(Cycle.ERROR, null, e);
}
}
@Override
public Cycle getCycle() {
return this.cycle;
}
/**
* 更新状态,并回调每个状态对应的函数
*
* @param cycle
* @param result
* @param e
*/
private void update(Cycle cycle, T result, Exception e) {
this.cycle = cycle;
if (this.lifecycle == null) {
return;
}
try {
switch (cycle) {
case STARTED:
this.lifecycle.onStart(currentThread());
break;
case RUNNING:
this.lifecycle.onRunning(currentThread());
break;
case DONE:
this.lifecycle.onFinish(currentThread(), result);
break;
case ERROR:
this.lifecycle.onError(currentThread(), e);
break;
default:
break;
}
} catch (Exception exception) {
// 在响应某个事件的过程中出现了意外,则会导致任务的正常执行受到影响,因此需要进行异常捕获,
// 并忽略这些异常信息以保证TaskLifecycle的实现不影响任务的正确执行,但是如果任务执行过程中出现错误并且抛出了异常,
// 那么update方法就不能忽略该异常,需要继续抛出异常,保持与call方法同样的意图。
if (cycle == Cycle.ERROR) {
throw exception;
}
}
}
}
4 测试
public class Client {
public static void main(String[] args) throws InterruptedException {
// 1 定义观察者
TaskLifecycle<String> taskLifecycle = new TaskLifecycle<String>() {
@Override
public void onStart(Thread thread) {
System.out.println("thread has start");
}
@Override
public void onRunning(Thread thread) {
System.out.println("thread will running");
}
@Override
public void onFinish(Thread thread, String result) {
System.out.println("thread has finished");
}
@Override
public void onError(Thread thread, Exception e) {
System.out.println("thread has error");
}
};
ObservableThread<String> observableThread = new ObservableThread(taskLifecycle,() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟异常
// int a = 1/0;
return "hello ObservableThread";
});
// 启动线程
observableThread.start();
System.out.println("当前线程的状态: " + observableThread.getCycle());
TimeUnit.SECONDS.sleep(1);
System.out.println("当前线程的状态: " + observableThread.getCycle());
TimeUnit.SECONDS.sleep(5);
System.out.println("当前线程的状态: " + observableThread.getCycle());
}
}
5 总结
- 在接口Observable中定义与Thread同样的方法用于屏蔽Thread的其他API,在使用的过程中使用Observable声明ObservableThread的类型,如果使用者还想知道更多的关于Thread的API,只需要在Observable接口中增加即可。
- 将ObservableThread中的run方法修饰为final,或者将ObservableThread类修饰为final,防止子类继承重写,导致整个生命周期的监控失效,我们都知道,任务的逻辑执行单元是存在于run方法之中的,而在ObservableThread中我们摒弃了这一点,让它专门监控业务执行单元的生命周期,而将真正的业务逻辑执行单元交给了一个可返回计算结果的接口Task。
- ObservableThread本身的run方法充当了事件源的发起者,而TaskLifecycle则扮演了事件回调的响应者。