Java 并发——Future详解
概念
Future
接口,表示的是异步计算(异步耗时任务)的结果,该接口提供了检查异步计算是否完成、等待异步调用完成、检索异步调用结果的方法。异步调用的结果只能通过该接口的get
及其重载方法来获取。但异步调用完成时,调用get可以获取到异步计算的结果,如果没有完成调用get就会阻塞直到异步调用完成,同时还提供了cancel
方法来异步任务的调用,isCanceled
和isDone
来判断异步任务是否取消或是否完成。
Future
接口设计出来的目的主要就是为了充分利用CPU,通过多线程,在一个线程执行耗时异步任务的同时,其他线程可以继续自己的工作,其他线程可以主动获取到耗时异步任务的执行结果。这里涉及到了Java多线程编程中的一个常用的多线程并行设计模式——Future模式。关于多线程并行设计模式,可以参考一下文章:
- 并行设计模式(一)—— Future模式
- 并行设计模式(二)— Master-Worker模式
- 并行设计模式(三)— Guarded Suspeionsion模式
- 并行设计模式(四)— 不变模式
- 并行设计模式(五)— 生产者-消费者模式
主要方法
cancel(boolean mayInterruptIfRunning)
,取消异步任务的执行,mayInterruptIfRunning
为true时表示会中断正在进行的任务,mayInterruptIfRunning
表示不会中断正在执行的任务;isCancelled()
,任务是否取消;isDone()
,任务是否执行完成;get()
,获取任务的结果,如果任务尚未执行完,会阻塞直到任务执行完;get(long timeout, TimeUnit unit)
,带超时等待的获取任务执行结果,规定时间还没有获取到结果的话,会报TimeoutException
异常。
类关系
FutureTask
ForkJoinTask
Java 设计模式——迭代器模式
模式定义
迭代器模式(Iterator Pattern),属于行为型模式,是 Java非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器在Java中有专门的Iterator
接口,Java集合类一般都实现了该接口,也就是说都支持通过迭代器来进行顺序元素访问。
模式示例
迭代器模式实现的关键就是迭代接口的定义了,一般需要定义以下方法:
- hashNext(),是否存在下一个元素;
- next(),获取下一个元素;
下面通过示例来实现迭代器模式,需要定义聚合对象,迭代器。下例主要完成对一组姓名信息的迭代输出(只为演示使用)。
定义迭代器接口
1 | public interface Iterator { |
定义Container接口,用于获取迭代器
1 | public interface Container { |
定义聚合对象,并实现迭代器接口
1 | public class NameRepository implements Container { |
编写客户端进行迭代访问
1 | public class IteratorPatternDemo { |
输出结果如下
1 | Name : Ethan |
注意:在需要对集合遍历并删除指定元素时,用迭代器可以很好的避免边移除边遍历造成的问题。
模式总结
从Java集合类大都实现了Iterator
这一点可以看出,迭代器模式肯定会给设计者带来诸多好处。它主要解决对聚合对象的遍历问题,可以通过定义不同的迭代器来完成不同方式的遍历。
优缺点
- 优点
- 它支持以不同的方式遍历一个聚合对象;
- 迭代器简化了聚合类;
- 在同一个聚合上可以有多个遍历;
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 缺点
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 为遍历不同的聚合结构提供一个统一的接口。
注意事项
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
Java 设计模式——解释器模式
模式定义
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
代码示例
下面这个示例(简单计算器实现),对输入的一段文本(表达式),这里主要支持数字、变量和加法和乘法
- 先根据定义的文本规则将其符号化,得到一个符号化列表,同时在最后面加上终止符号,然后返回符号化列表,
- 然后根据规则取出token,将其组合解释成表达式。具体规则如下:
- 开头必须是变量或者数字;
- 先处理乘法在处理加法
- 得到表达式后,对表达式进行解析处理,然后得到结果。
具体代码如下:
定义支持的类型(变量、数字、加、乘、空格、终止符)
1 | /** |
1 | /** |
1 | /** |
1 | /** |
一下部分开始定义表达式
先是接口
1 | /** |
然后是四种类型的解释器
1 | /** |
1 | /** |
1 | /** |
1 | /** |
由于可能会存在解释异常,所以定义了一个异常类
1 | public class InterpreterException extends Exception { |
编写客户端运行
1 | public class Main { |
输出结果
1 | Attempting to evaluate: age * 10 + height * 1.5 |
从代码量来看,这个示例还是比较复杂的,由此可见编译器实现的复杂程度。
模式总结
对于一些固定文法构建一个解释句子的解释器。如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。注意由于解释器模式实现方式通常会或多或少的存在使用迭代(或递归)的场景,所以一定要做好终止条件的判断。
优缺点
优点
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式。
- 易于实现简单文法。
缺点
- 可利用场景比较少;
- 对于复杂的文法比较难维护;
- 解释器模式会引起类膨胀;
- 解释器模式采用递归调用方法;
使用场景
通常使用在编译器、运算表达式计算中,另外Xml解析、Json解析都或多或少体现了解释器模式的应用。
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 一些重复出现的问题可以用一种简单的语言来进行表达。
- 一个简单语法需要解释的场景。
注意事项
- 需要注意构建语法树,定义终结符与非终结符的方式。
- 可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。
- 构建环境类来描述解释器运行的所需的环境,包含解释器之外的一些全局信息,一般是 HashMap。
Java 设计模式——状态模式
模式定义
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
模式示例
状态模式需要定义表示各种状态的对象,同时还要有一个行为随着状态对象改变而改变的 Context
对象,这个Context对象通常会关联一个状态,通过这个Context
,可已调用某个状态对象具体的执行方法。下面通过电视机遥控器这个case来描述状态模式。
电视机遥控器可以控制电视机的开(Start)和关(Stop)两种状态,我们需要知道遥控器需要切换到那个状态,这里用 TvContext
,这个TvContext
包含了一个状态对象和一个切换方法,这个方法实际上调用的这个状态对象来处理的。
- 定义状态接口,
State
; - 实现状态接口,TvStartState,TvStopState;
- 定义一个类表示当前的所处的环境,TvContext;
定义状态接口
1 | /** |
实现状态接口
1 | /** |
1 | /** |
实现上下文对象
1 | /** |
编写控制终端
1 | /** |
运行结果
1 | TV is turned ON |
模式总结
状态模式通常应用在对象行为随状态改变的情形下,当然也可以用状态模式来消除if...else
判断。状态模式和命令模式有很多相似之处:
- 通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。
- 状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。
- 状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
优缺点
- 优点
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
- 缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景
对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。所以当代码中含大量与对象状态有关的条件语句时可以考虑使用状态模式来实现。
- 行为随状态改变而改变的场景。
- 条件、分支语句的代替者。
注意事项
在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
介绍
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
Java 设计模式——访问者模式
模式定义
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
模式示例
下面以电脑启动时对各个组成部分的访问为例来说明访问者模式。主要以下几个步骤
- 定义元素接口,被访问者需要实现这个接口;
- 定义访问者接口,访问者需要实现该接口;
- 具体实现被访问者类和访问者类
- 编写客户端验证
定义元素接口
1 | /** |
定义访问者接口
1 | /** |
这里给访问者传递的都是具体的类,如果被访问者新增元素,需要增加相应的方法,同时访问者类的实现者需要实现该方法,违反了迪米特原则。
具体实现被访问者和访问者
1 | /** |
这是元素对象,本身可被访问,同时其组成部分也可被访问。
1 | /** |
1 | /** |
1 | /** |
定义访问者,先看整体访问者
1 | /** |
输入设备访问者
1 | /** |
用来验证的客户端
1 | public class VisitorPatternDemo { |
输出结果:
1 | == Show all computer part: == |
可以看到,访问者还是可能会有一些方法的冗余的,比如UserInputVisitor
,不需要访问Monitor
和Computer
但却不得不实现访问者接口定义的方法(空实现)
模式总结
使用访问者模式时,通常这个类(元素类)的结构(数据结构)比较固定,主要解决稳定的数据结构和易变的操作耦合问题。
优缺点
- 优点
- 符合单一职责原则。
- 优秀的扩展性。
- 灵活性。
- 缺点
- 具体元素对访问者公布细节,违反了迪米特原则(一个对象应该尽可能的减少对其他元素的了解)。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景
对固定多部分组成对象元素进行不同的操作时可以用到该模式。如电脑的结构固定,我可以定义一个访问者只访问输入设备,也可以定义一个访问者访问显示设备。
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类。
注意事项
- 如果元素经常变动,结构不固定,就不适合使用访问者模式了,因为该模式增加新元素比较困难;
- 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
- 在数据基础类里面有一个方法接受访问者,将自身引用传入访问者,已让访问者访问。
- 在被访问的类里面加一个对外提供接待访问者的接口。
Java设计模式——观察者模式
模式定义
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
模式示例
Java本身对观察者模式的实现有良好的支持,被观察者类只要继承java.util.Observable
即可,观察者类只要实现java.util.Observer
接口即可。下面写一个Demo来描述观察者模式,这个示例如下,一个新闻事件中心,可以能会发生各种事件,有几家报社在新闻事件中心发生事件后,会根据事件做相应的报道,这是典型的一对多关系。
- 定义被观察者接口,新闻中心实现被观察者接口,支持事件通知、添加或移除报社(观察者)操作;
- 定义观察者接口,报社要实现该接口,当事件发生时,可以对事件进行响应。
- 定义客户端来演示
定义被观察者
1 | /** |
定义观察者接口
1 | /** |
定义真正被观察者NewsData,实现Subject接口
1 | /** |
定义四家报社(真正的观察者)这四家报社对新闻的关注点不同,可以选择性的报道:
1 | /** |
1 | /** |
1 | /** |
1 | /** |
定义客户端,进行新闻播报
1 | public class NewsStation { |
输出结果
1 | Ccc headlineNews: 'Big objects found' in AirAsia hunt |
模式总结
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。观察者和被观察者是抽象耦合的,被观察者持有一个观察者列表,在自身发生变化时,遍历该列表逐一通知。
观察者模式主要解决,一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
优缺点
- 优点
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
- 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
- 可能产生循环依赖的问题,如观察者和被观察者间可能存在循环依赖的问题(EventBus使用时就会产生这种问题)
- 观察者模式只能告诉观察者被观察者发生了变化,但不能说明具体发生了什么变化。
使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项
- JAVA 中已经有了对观察者模式的支持类。
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
Java 设计模式14——命令模式
模式定义
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。模式的核心思想将一个请求封装成一个对象,从而可以用不同的请求对客户进行参数化。
实现
定义三个角色:
- RealCommandExecutor, 真正的命令执行对象;
- Command,封装了具体命令执行者的命令对象
- Invoke,使用命令对象的入口
模式示例
举一个例子,一个控制终端,可以控制5种不同的角色进行攻击,这5种角色就是上面的真正命令执行对象,5中角色分别对应5个Command,调用者可以通过控制终端让执行不同的命令以让不同角色进行攻击。下面看具体代码。
1 | public interface Command { |
定义5中不同的具体命令执行对象。
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
将不同角色的攻击请求封装成Command命令
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
定义一个远程控制终端
1 | /** |
编写客户端运行
1 | public class FightMain { |
输出结果
1 | ------------Remote Control------------ |
从上例可以看出,我们可以很容易的新增一种角色,然后定义该角色的攻击方式,可以使单次攻击(attack里面调用一个攻击方法),也可以是组合攻击(attack里面调用两个攻击方法),还可以利用组合模式,将一组命令封装成Command对象来执行。当然前提是要有相应的角色,要有相应的命令,也就是说要有相应的类。这就可能造成命令泛滥的情形,需要加以控制。
模式总结
优缺点
- 优点
- 降低了系统耦合度;
- 新的命令可以很容易添加到系统中去。
- 缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类;
使用场景
认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。
注意事项
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式。关于命令模式的扩展,可以参考这篇文章。
Java 设计模式11——享元模式
享元模式
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
实现方式
- 采用
HashMap
或者其他支持KV访问的数据结构来存储这些创建的对象; - 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象;
代码示例
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。具体步骤如下:
- 定义Shape接口;
- 定义Circle对象;
- 定义产生Circle对象的工厂;
- 示例运行
1 | public interface Shape { |
1 | public class Circle implements Shape { |
1 | public class ShapeFactory { |
1 | public class FlyWeightDemo { |
输出结果如下:
1 | ---------------------------------- |
模式总结
享元模式主要运用共享技术有效地支持大量细粒度的对象。在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
优缺点
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景
- 系统中有大量对象。
- 这些对象消耗大量内存。
- 这些对象的状态大部分可以外部化。
- 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替
- 系统不依赖于这些对象身份,这些对象是不可分辨的。
应用实例
- JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
- 数据库的数据池。
注意事项
- 注意划分外部状态和内部状态,否则可能会引起线程安全问题。 示例中color对一个Circle来说是不变的,属于内部状态,而x、y、radius则属于外部状态,可以又外界直接改变。
- 这些类必须有一个工厂对象加以控制。