在前面的文章中介绍过 观察者模式 及 并发编程 的基础知识,为了让大家更好的了解观察者模式
故而特意写了这篇番外..
概述 在Java多线程下,我们需要知道当前执行线程的状态是什么比如运行
,关闭
,异常
等状态的通知,而且不仅仅是更新当前页面。
观察者模式: 是一种使用率极高的模式,用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式传送门:http://blog.battcn.com/2017/12/11/java/design-pattern/observer-pattern/#more
案例 假设开发一个多线程爬虫功能
,由于数据过大需要利用多线程并行化来提升抓取的效率,并且在抓取过程中要记录执行线程的运行状态以便追溯问题原因
UML图如下
1.定义具体观察
对象,实现JDK 自带的Observer
接口,然后在需要实现的update
方法中记录下每个线程执行的状态信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ObserverListener implements Observer { private static final Object LOCK = new Object(); @Override public void update (Observable observable, Object runnableEvent) { synchronized (LOCK) { ObservableRunnable.RunnableEvent event = (ObservableRunnable.RunnableEvent) runnableEvent; if (event != null ) { if (event.getCause() != null ) { System.out.println("The Runnable [" + event.getThread().getName() + "] process failed and state is " + event.getState().name()); event.getCause().printStackTrace(); } else { System.out.println("The Runnable [" + event.getThread().getName() + "] data changed and state is " + event.getState().name()); } } } } }
2.定义具体被观察
的对象,该对象需要继承Observable
类,以及实现Runnable
接口,这里run
的实现非常简单,执行每一步骤操作时都进行了通知,通知观察者
消息发生变更了
为什么每次都需要 setChanged
呢
筛选有效通知,只有有效通知可以调用setChanged。比如,我的微信朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A。
便于撤销通知操作,在主题中,我们可以设置很多次setChanged
,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged
轻松解决问题。
主动权控制,由于setChanged
为protected
,而notifyObservers
方法为public
,这就导致存在外部随意调用notifyObservers
的可能,但是外部无法调用setChanged
,因此真正的控制权应该在主题这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class ObservableRunnable extends Observable implements Runnable { private String name; ObservableRunnable(String name, ObserverListener listener) { this .name = name; super .addObserver(listener); } private void notifyChange (final RunnableEvent event) { super .setChanged(); super .notifyObservers(event); } @Override public void run () { try { notifyChange(new RunnableEvent(RunnableState.RUNNING, Thread.currentThread(), null )); System.out.printf("根据 [%s] 查询 \n" , this .name); Thread.sleep(1000L ); if (this .name.equals("T3" )) { throw new RuntimeException("故意抛出错误" ); } notifyChange(new RunnableEvent(RunnableState.DOWN, Thread.currentThread(), null )); } catch (Exception e) { notifyChange(new RunnableEvent(RunnableState.ERROR, Thread.currentThread(), e)); } } enum RunnableState { RUNNING, ERROR, DOWN } static class RunnableEvent { private final RunnableState state; private final Thread thread; private final Throwable cause; RunnableEvent(RunnableState state, Thread thread, Throwable cause) { this .state = state; this .thread = thread; this .cause = cause; } RunnableState getState () { return state; } public Thread getThread () { return thread; } Throwable getCause () { return cause; } } }
3.创建测试工程
1 2 3 4 5 6 7 8 9 10 11 public class ObserverClient { public static void main (String[] args) { ObserverListener listener = new ObserverListener(); List<String> names = Arrays.asList("T1" , "T2" , "T3" ); for (String name : names) { Thread thread = new Thread(new ObservableRunnable(name, listener)); thread.start(); } } }
4.运行结果,通过运行日志可以发现,启动三个线程后同时执行抓取操作 ,但是Thread-2
线程在数据处理时发生了异常,在ObserverListener
处也成功收到通知的内容,然后对信息进行了输出操作。在实际过程中我们可以为异常进行补偿操作
1 2 3 4 5 6 7 8 9 10 11 12 The Runnable [Thread-1 ] data changed and state is RUNNING The Runnable [Thread-0 ] data changed and state is RUNNING 根据 [T1] 查询 The Runnable [Thread-2 ] data changed and state is RUNNING 根据 [T2] 查询 根据 [T3] 查询 java.lang.RuntimeException: 故意抛出错误 The Runnable [Thread-0 ] data changed and state is DOWN at com.battcn.chapter14.ObservableRunnable.run(ObserverClient.java:67 ) The Runnable [Thread-1 ] data changed and state is DOWN at java.lang.Thread.run(Thread.java:745 ) The Runnable [Thread-2 ] process failed and state is ERROR
总结 本文,简单讲述了多线程环境下如何利用观察者模式进行线程状态监听 ,也是对前面所讲的基础进行巩固,在学习的过程中,既要知其然也要知其所以然。这样才能更好地驾驭它,更好地去理解和使用,也能更好地帮助我们触类旁通
。
- 说点什么 全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter14
个人QQ:1837307557
battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)