Java多线程编程实战指南(设计模式篇,黄文海)-之管道线模式

来源:互联网 发布:新浪微博 爬虫 python 编辑:程序博客网 时间:2024/05/05 22:31

不得不说,本人工作上很少有使用多线程编程技术的地方。由于本人工作上经常使用的是类似SSH等框架搭建MVC架构,所以更加习惯于编写一些优秀程序员所唾弃的样板式的代码。最近看了文海的多线程编程实战指南,瞬间眼前一亮。觉得有很多自己可以学习的,事实上,我已经在最近的项目中使用上了那本书介绍的两相终止模式、串行封闭模式、生产者消费者模式以及线程池等技术,确实在许多方面改进了我们服务端的吞吐量。说到这里本人吐槽一下,由于是毕业后转行,目前也才工作一年还不满2个月。所以原谅我的得瑟,但我相信我以后会做的更好!


个人觉的那是一本实战性极强的书,很有阅读价值。但我本人不是来打广告的。废话少说,进入正题。我今天要讲的是我对书中的管道线模式的理解。所谓管道线模式,我的理解就是将一个任务分成多个阶段,任务的完成需要经过所有的阶段才能完成。然后,倘若采用多线程的模式,使得任务的不同阶段在不同的线程中完成。那么,客户端或客户端代码提交的任务只要在阶段一处理完成就返回了,尽管该任务或许还在处理中。这样的好处呢就是减少了客户端代码的等待。但事实上任何一个具体的任务依然是串行执行的,这样可以避免某些令人纠结的线程安全问题。但假如客户端代码同时并发的提交了多个任务,那么处理任务一的阶段一的线程在处理完这个具体阶段后就可以紧接着处理任务二的阶段一,尽管任务一可能还处于阶段二或者阶段三等。所以,在多任务提交的情况下。管道线模式又有并发执行的效果,我们姑且称为伪并发吧。


之所以写下这篇博客,是因为管道线模式涉及的其他模式太多,这个模式下可以涉及线程池的使用,能找到责任链模式的影子,可以运用上装饰器 模式,以及多线程编程中的两相终止模式等等。所以有利于巩固自己所学,并增加自己的综合实战能力。哈哈,不说了,我们来具体的看代码吧。


首先,这是对各个管道的抽象,由于任务是在各个管道中顺序处理的,任务在一个管道中处理到某个阶段后必然要流转到下一个管道,所以接口中定义了设置下一个管道的setNextPipe()方法等,还在初始化方法中定义了如何传递上下文信息等,其余不再赘述:

<span style="font-size:18px;">public interface Pipe<IN, OUT>{/** * 设置当前Pipe实例的下一个Pipe实例。 * @param nextPipe *            下一个Pipe实例 */void setNextPipe(Pipe<?, ?> nextPipe);/** * 初始化当前Pipe实例对外提供的服务。 * @param pipeCtx */void init(PipeContext pipeCtx);/** * 停止当前Pipe实例对外提供的服务。 *  * @param timeout * @param unit */void shutdown(long timeout, TimeUnit unit);/** * 对输入元素进行处理,并将处理结果作为下一个Pipe实例的输入。 */void process(IN input) throws InterruptedException;}</span>



接下来我们看一看对上下文的抽象,注意这个接口是对各个处理阶段的计算环境进行抽象,主要用于异常处理。

<span style="font-size:18px;">public interface PipeContext{/** * 用于对处理阶段抛出的异常进行处理. *  * @param exp */void handleError(PipeException exp);}</span>



从管道上下文接口中可以看到对各个管道中可能抛出的异常进行了统一处理,那我们就先来看一看这个自定义的管道异常对象(PipeException)吧:

<span style="font-size:18px;">public class PipeException extends Exception{private static final long serialVersionUID = -2944728968269016114L;/** * 抛出异常的Pipe实例。 */public final Pipe<?, ?> sourcePipe;/** * 抛出异常的Pipe实例在抛出异常时所处理的输入元素。 */public final Object input;public PipeException(Pipe<?, ?> sourcePipe, Object input, String message){super(message);this.sourcePipe = sourcePipe;this.input = input;}public PipeException(Pipe<?, ?> sourcePipe, Object input, String message, Throwable cause){super(message, cause);this.sourcePipe = sourcePipe;this.input = input;}}</span>


好,接着我们看一个对管道的抽象实现类,

<span style="font-size:18px;">public abstract class AbstractPipe<IN, OUT> implements Pipe<IN, OUT>{protected volatile Pipe<?, ?> nextPipe = null;protected volatile PipeContext pipeCtx;@Overridepublic void init(PipeContext pipeCtx){this.pipeCtx = pipeCtx;}@Overridepublic void setNextPipe(Pipe<?, ?> nextPipe){this.nextPipe = nextPipe;}@Overridepublic void shutdown(long timeout, TimeUnit unit){// 什么也不做}/** * 留给子类实现。用于子类实现其任务处理逻辑。 *  * @param input *            输入元素(任务) * @return 任务的处理结果 * @throws PipeException */protected abstract OUT doProcess(IN input) throws PipeException;@SuppressWarnings("unchecked")public void process(IN input) throws InterruptedException{try{OUT out = doProcess(input);if (null != nextPipe){if (null != out){((Pipe<OUT, ?>) nextPipe).process(out);}}}catch (InterruptedException e){Thread.currentThread().interrupt();}catch (PipeException e){pipeCtx.handleError(e);}}}</span>


既然任务是要在各个管道中顺序处理的,一个管道根据输入得到了某种中间结果,并作为下一个管道的输入,直到得到最终结果,那么,这些管道是怎么串联起来的呢?接下来的接口就是定义如何串联各个管道的管道线接口,仔细看一看并好好想想吧,是不是能找到责任链模式的影子呢,哈哈。

<span style="font-size:18px;">public interface Pipeline<IN, OUT> extends Pipe<IN, OUT>{/** * 往该Pipeline实例中添加一个Pipe实例。 *  * @param pipe *            Pipe实例 */void addPipe(Pipe<?, ?> pipe);}</span>

光有一个接口还是看不出啥名堂哈,且看管道线的具体实现,可以看到管道线对象有一个添加管道的方法加入了不同的管道,并在初始化过程中设置了各个管道的下一个管道是谁。另外,初始化任务是构建成线程对象并提交给管道线对象持有的线程池对象helperExecutor完成的,添加方法还有两个重载方法是addAsThreadPoolBasedPipe和addAsWorkerThreadBasedPipe,这两个方法添加进的管道对象是被装饰过的,我前面不是说过管道线模式还涉及到装饰模式吗,装饰模式就是在这里体现的哦,哈哈:

<span style="font-size:18px;">public class SimplePipeline<T, OUT> extends AbstractPipe<T, OUT> implements Pipeline<T, OUT>{private final Queue<Pipe<?, ?>> pipes = new LinkedList<Pipe<?, ?>>();private final ExecutorService helperExecutor;public SimplePipeline(){this(Executors.newSingleThreadExecutor(new ThreadFactory(){@Overridepublic Thread newThread(Runnable r){Thread t = new Thread(r, "SimplePipeline-Helper");t.setDaemon(true);return t;}}));}public SimplePipeline(final ExecutorService helperExecutor){super();this.helperExecutor = helperExecutor;}@Overridepublic void shutdown(long timeout, TimeUnit unit){Pipe<?, ?> pipe;while (null != (pipe = pipes.poll())){pipe.shutdown(timeout, unit);}helperExecutor.shutdown();}@Overrideprotected OUT doProcess(T input) throws PipeException{// 什么也不做return null;}@Overridepublic void addPipe(Pipe<?, ?> pipe){// Pipe间的关联关系在init方法中建立pipes.add(pipe);}public <INPUT, OUTPUT> void addAsWorkerThreadBasedPipe(Pipe<INPUT, OUTPUT> delegate, int workerCount){addPipe(new WorkerThreadPipeDecorator<INPUT, OUTPUT>(delegate, workerCount));}public <INPUT, OUTPUT> void addAsThreadPoolBasedPipe(Pipe<INPUT, OUTPUT> delegate, ExecutorService executorSerivce){addPipe(new ThreadPoolPipeDecorator<INPUT, OUTPUT>(delegate, executorSerivce));}@Overridepublic void process(T input) throws InterruptedException{@SuppressWarnings("unchecked")Pipe<T, ?> firstPipe = (Pipe<T, ?>) pipes.peek();firstPipe.process(input);}@Overridepublic void init(final PipeContext ctx){LinkedList<Pipe<?, ?>> pipesList = (LinkedList<Pipe<?, ?>>) pipes;Pipe<?, ?> prevPipe = this;for (Pipe<?, ?> pipe : pipesList){prevPipe.setNextPipe(pipe);prevPipe = pipe;}Runnable task = new Runnable(){@Overridepublic void run(){for (Pipe<?, ?> pipe : pipes){pipe.init(ctx);}}};helperExecutor.submit(task);}public PipeContext newDefaultPipelineContext(){return new PipeContext(){@Overridepublic void handleError(final PipeException exp){helperExecutor.submit(new Runnable(){@Overridepublic void run(){exp.printStackTrace();}});}};}}</span>



上面的代码中涉及两个对管道线的装饰类,一个是WorkerThreadPipeDecorator,这一个装饰器我就不贴出来了,因为接下来的demo也用不到,而且本文的代码量有些多,要是有人想要实际运行这个demo,把用到这个装饰类的添加管道的重载方法注释掉就行啦。另一个是ThreadPoolPipeDecorator。说白了就是将管道的任务委托线程池执行啦,当然这个执行过程对客户端代码来说是透明的,装饰以后仍然是一个管道哦,这就是装饰模式的意图哦,具体看代码吧:

<span style="font-size:18px;">public class ThreadPoolPipeDecorator<IN, OUT> implements Pipe<IN, OUT>{private final Pipe<IN, OUT> delegate;private final ExecutorService executorSerivce;// 线程池停止标志。private final TerminationToken terminationToken;private final CountDownLatch stageProcessDoneLatch = new CountDownLatch(1);public ThreadPoolPipeDecorator(Pipe<IN, OUT> delegate, ExecutorService executorSerivce){this.delegate = delegate;this.executorSerivce = executorSerivce;this.terminationToken = TerminationToken.newInstance(executorSerivce);}@Overridepublic void init(PipeContext pipeCtx){delegate.init(pipeCtx);}@Overridepublic void process(final IN input) throws InterruptedException{Runnable task = new Runnable(){@Overridepublic void run(){int remainingReservations = -1;try{delegate.process(input);}catch (InterruptedException e){;}finally{remainingReservations = terminationToken.reservations.decrementAndGet();}if (terminationToken.isToShutdown() && 0 == remainingReservations){stageProcessDoneLatch.countDown();}}};executorSerivce.submit(task);terminationToken.reservations.incrementAndGet();}@Overridepublic void shutdown(long timeout, TimeUnit unit){terminationToken.setIsToShutdown();if (terminationToken.reservations.get() > 0){try{if (stageProcessDoneLatch.getCount() > 0){stageProcessDoneLatch.await(timeout, unit);}}catch (InterruptedException e){;}}delegate.shutdown(timeout, unit);}@Overridepublic void setNextPipe(Pipe<?, ?> nextPipe){delegate.setNextPipe(nextPipe);}/** * 线程池停止标志。 每个ExecutorService实例对应唯一的一个TerminationToken实例。 这里使用了Two-phase * Termination模式(第5章)的思想来停止多个Pipe实例所共用的 线程池实例。 *  * @author Viscent Huang * */private static class TerminationToken extends io.github.viscent.mtpattern.ch5.tpt.TerminationToken{private final static ConcurrentMap<ExecutorService, TerminationToken> INSTANCES_MAP = new ConcurrentHashMap<ExecutorService, TerminationToken>();// 私有构造器private TerminationToken(){}void setIsToShutdown(){this.toShutdown = true;}static TerminationToken newInstance(ExecutorService executorSerivce){TerminationToken token = INSTANCES_MAP.get(executorSerivce);if (null == token){token = new TerminationToken();TerminationToken existingToken = INSTANCES_MAP.putIfAbsent(executorSerivce, token);if (null != existingToken){token = existingToken;}}return token;}}}</span>


好了,有了以上定义的各个类,我们来实际的使用一下管道线模式吧,这个例子也是多线程编程实战之南一书中给的,是一个基于线程池的管道例子,只不过我依葫芦画瓢的又多增加了两条管道而已,这个例子中,注意各个管道是被ThreadPoolPipeDecorator装饰后才添加到管道线中的哦,管道中的任务是委托线程池来执行的。

<span style="font-size:18px;">public class ThreadPoolBasedPipeExample{/** * 主函数 *  * @param args *            void * @author lihong 2016年4月26日 下午2:43:54 * @since v1.0 */public static void main(String[] args){/* * 创建线程池 */final ThreadPoolExecutor executorSerivce;executorSerivce = new ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors() * 2, 60, TimeUnit.MINUTES, new SynchronousQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());/* * 创建管道线对象 */final SimplePipeline<String, String> pipeline = new SimplePipeline<String, String>();/* * 创建第一条管道 */Pipe<String, String> pipe = new AbstractPipe<String, String>(){@Overrideprotected String doProcess(String input) throws PipeException{String result = input + "->[pipe1," + Thread.currentThread().getName() + "]";System.out.println(result);return result;}};/* * 将第一条管道加入线程池 */pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);/* * 创建第二条管道 */pipe = new AbstractPipe<String, String>(){@Overrideprotected String doProcess(String input) throws PipeException{String result = input + "->[pipe2," + Thread.currentThread().getName() + "]";System.out.println(result);try{Thread.sleep(new Random().nextInt(100));}catch (InterruptedException e){;}return result;}};/* * 将第二条管道加入管道线 */pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);/* * 创建第三条管道 */pipe = new AbstractPipe<String, String>(){@Overrideprotected String doProcess(String input) throws PipeException{String result = input + "->[pipe3," + Thread.currentThread().getName() + "]";;System.out.println(result);try{Thread.sleep(new Random().nextInt(200));}catch (InterruptedException e){;}return result;}};/* * 将第三条管道加入管道线 */pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);/* * 第四条 */pipe = new AbstractPipe<String, String>(){@Overrideprotected String doProcess(String input) throws PipeException{String result = input + "->[pipe4," + Thread.currentThread().getName() + "]";;System.out.println(result);try{Thread.sleep(new Random().nextInt(200));}catch (InterruptedException e){;}return result;}};/* * 将第四条管道加入管道线 */pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);/* * 创建第五条 */pipe = new AbstractPipe<String, String>(){@Overrideprotected String doProcess(String input) throws PipeException{String result = input + "->[pipe5," + Thread.currentThread().getName() + "]";;System.out.println(result);try{Thread.sleep(new Random().nextInt(200));}catch (InterruptedException e){;}return result;}@Overridepublic void shutdown(long timeout, TimeUnit unit){// 在最后一个Pipe中关闭线程池executorSerivce.shutdown();try{executorSerivce.awaitTermination(timeout, unit);}catch (InterruptedException e){;}}};/* * 将第五条管道加入管道线 */pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);/* * 管道线初始化 */pipeline.init(pipeline.newDefaultPipelineContext());int N = 100;try{for (int i = 0; i < N; i++){pipeline.process("Task-" + i);}}catch (IllegalStateException e){;}catch (InterruptedException e){;}pipeline.shutdown(10, TimeUnit.SECONDS);}}</span>

我们来看一下这个demo同时执行5个任务的运行结果吧





我们具体看一下任务四的执行过程:
<span style="font-size:18px;">Task-4->[pipe1,pool-1-thread-5]Task-4->[pipe1,pool-1-thread-5]->[pipe2,pool-1-thread-4]Task-4->[pipe1,pool-1-thread-5]->[pipe2,pool-1-thread-4]->[pipe3,pool-1-thread-3]Task-4->[pipe1,pool-1-thread-5]->[pipe2,pool-1-thread-4]->[pipe3,pool-1-thread-3]->[pipe4,pool-1-thread-6]Task-4->[pipe1,pool-1-thread-5]->[pipe2,pool-1-thread-4]->[pipe3,pool-1-thread-3]->[pipe4,pool-1-thread-6]->[pipe5,pool-1-thread-1]</span>

通过上面的代码可以看到,task-4依次流经了5个管道,但在各个管道中的具体处理动作是由线程池的不同工作者线程处理的。task-4在第1、2、3、4、5五个管道中的动作分别由线程池的工作者线程6、4、3、6、1执行。再总结一下哦,管道线模式中单个任务是循序执行的,但多个任务同时执行是有并发的效果的,因为一个任务的某个阶段刚出里完,就可以接着处理另外一个任务的相同阶段,尽管这两个任务都还没完成。这样就不必等一个任务的所有阶段都执行完毕才能接着处理另外一个任务

1 0
原创粉丝点击