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

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


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


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


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

import java.util.concurrent.TimeUnit;

public interface Pipe {
    /**
     * 设置当前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;
}



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

public interface PipeContext
{
    /**
     * 用于对处理阶段抛出的异常进行处理.
     *
     * @param exp
     */
    void handleError(PipeException exp);
}

接下来的这个类就是管道线模式中封装的异常对象:

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;
    }
}

好了,我们接下来看看基于管道接口封装的抽线管道类:

import java.util.concurrent.TimeUnit;

public abstract class AbstractPipe implements Pipe
{
    protected volatile Pipe nextPipe = null;
    protected volatile PipeContext pipeCtx;

    @Override
    public void init(PipeContext pipeCtx)
    {
        this.pipeCtx = pipeCtx;

    }

    @Override
    public void setNextPipe(Pipe nextPipe)
    {
        this.nextPipe = nextPipe;

    }

    @Override
    public 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) nextPipe).process(out);
                }
            }
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        catch (PipeException e)
        {
            pipeCtx.handleError(e);
        }
    }
}

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

public interface Pipeline extends Pipe
{

    /**
     * 往该Pipeline实例中添加一个Pipe实例。
     *
     * @param pipe
     *            Pipe实例
     */
    void addPipe(Pipe pipe);
}


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

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class SimplePipeline extends AbstractPipe implements Pipeline
{

    private final Queue> pipes = new LinkedList>();

    private final ExecutorService helperExecutor;

    public SimplePipeline()
    {
        this(Executors.newSingleThreadExecutor(new ThreadFactory()
        {

            @Override
            public Thread newThread(Runnable r)
            {
                Thread t = new Thread(r, "SimplePipeline-Helper");
                t.setDaemon(false);
                return t;
            }

        }));

    }

    public SimplePipeline(final ExecutorService helperExecutor)
    {
        super();
        this.helperExecutor = helperExecutor;
    }

    @Override
    public void shutdown(long timeout, TimeUnit unit)
    {
        Pipe pipe;

        while (null != (pipe = pipes.poll()))
        {
//            System.out.println("SimplePipeLine调用管道的关闭方法的线程是:  " + Thread.currentThread().getName());
            pipe.shutdown(timeout, unit);
        }

        helperExecutor.shutdown();

    }

    @Override
    protected OUT doProcess(T input) throws PipeException
    {
        // 什么也不做
        return null;
    }

    @Override
    public void addPipe(Pipe pipe)
    {

        // Pipe间的关联关系在init方法中建立
        pipes.add(pipe);
    }


    public  void addAsThreadPoolBasedPipe(Pipe delegate, ExecutorService executorSerivce)
    {
        addPipe(new ThreadPoolPipeDecorator(delegate, executorSerivce));
    }

    @Override
    public void process(T input) throws InterruptedException
    {

        @SuppressWarnings("unchecked")
        Pipe firstPipe = (Pipe) pipes.peek();

        firstPipe.process(input);
    }

    @Override
    public void init(final PipeContext ctx)
    {

        LinkedList> pipesList = (LinkedList>) pipes;
        Pipe prevPipe = this;
        for (Pipe pipe : pipesList)
        {
            prevPipe.setNextPipe(pipe);
            prevPipe = pipe;
        }

        Runnable task = new Runnable()
        {
            @Override
            public void run()
            {
                for (Pipe pipe : pipes)
                {
                    pipe.init(ctx);
                }

            }

        };

        helperExecutor.submit(task);
    }

    public PipeContext newDefaultPipelineContext()
    {
        return new PipeContext()
        {
            @Override
            public void handleError(final PipeException exp)
            {
                helperExecutor.submit(new Runnable()
                {

                    @Override
                    public void run()
                    {
                        exp.printStackTrace();
                    }

                });
            }

        };
    }
}


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

public class ThreadPoolPipeDecorator implements Pipe
{
    private String name = "->";
    private final Pipe delegate;
    private final ExecutorService executorSerivce;

    // 线程池停止标志。
    private final TerminationToken terminationToken;
    private final CountDownLatch stageProcessDoneLatch = new CountDownLatch(1);

    public ThreadPoolPipeDecorator(Pipe delegate, ExecutorService executorSerivce)
    {
        this.delegate = delegate;
        this.executorSerivce = executorSerivce;
        this.terminationToken = TerminationToken.newInstance(executorSerivce);
    }

    @Override
    public void init(PipeContext pipeCtx)
    {
        delegate.init(pipeCtx);

    }

    @Override
    public void process(final IN input) throws InterruptedException
    {

        Runnable task = new Runnable()
        {
            @Override
            public void run()
            {
                int remainingReservations = -1;
                try
                {
                    delegate.process(input);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    remainingReservations = terminationToken.reservations.decrementAndGet();
//                    System.out.println("剩余任务数量是:" + remainingReservations);
                }

                if (terminationToken.isToShutdown() && 0 == remainingReservations)
                {
                    stageProcessDoneLatch.countDown();
                }
            }

        };

        executorSerivce.submit(task);

        int i = terminationToken.reservations.incrementAndGet();
//        System.out.println("当前任务数量是:" + i);

    }

    @Override
    public void shutdown(long timeout, TimeUnit unit)
    {
        terminationToken.setIsToShutdown();

        if (terminationToken.reservations.get() > 0)
        {
            try
            {
                if (stageProcessDoneLatch.getCount() > 0)
                {
                    //System.out.println("Decorator调用管道的关闭方法的线程是:  " + Thread.currentThread().getName());
                    stageProcessDoneLatch.await(timeout, unit);
                }
            }
            catch (InterruptedException e)
            {
                ;
            }
        }

//        System.out.println("Decorator调用delegate的关闭方法的线程是:  " + Thread.currentThread().getName());
        delegate.shutdown(timeout, unit);
    }

    @Override
    public void setNextPipe(Pipe nextPipe)
    {
        delegate.setNextPipe(nextPipe);
    }

    /**
     * 线程池停止标志。 每个ExecutorService实例对应唯一的一个TerminationToken实例。 这里使用了Two-phase
     * Termination模式(第5章)的思想来停止多个Pipe实例所共用的 线程池实例。
     *
     * @author Viscent Huang
     *
     */
    private static class TerminationToken extends GeneralTerminationToken
    {
        private final static ConcurrentMap INSTANCES_MAP = new ConcurrentHashMap();

        // 私有构造器
        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;
                }
            }
            System.out.println(Thread.currentThread().getName() + token);

            return token;
        }
    }

}


另外,管道线模式还有该书中另外一种设计模式-“两相终止模式“的影子,为了表示线程是可终止的,定义了如下接口:

public interface Terminatable {
	void terminate();
}

为了记录当前的任务量,书中使用如下这个类记录积压的任务量:

import java.lang.ref.WeakReference;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class GeneralTerminationToken {

	// 使用volatile修饰,以保证无需显式锁的情况下该变量的内存可见性
	protected volatile boolean toShutdown = false;
	public final AtomicInteger reservations = new AtomicInteger(0);

	/*
	 * 在多个可停止线程实例共享一个TerminationToken实例的情况下,该队列用于
	 * 记录那些共享TerminationToken实例的可停止线程,以便尽可能减少锁的使用 的情况下,实现这些线程的停止。
	 */
	private final Queue> coordinatedThreads;

	public GeneralTerminationToken() {
		coordinatedThreads = new ConcurrentLinkedQueue>();
	}

	public boolean isToShutdown() {
		return toShutdown;
	}

	protected void setToShutdown(boolean toShutdown) {
		this.toShutdown = true;
	}

	protected void register(Terminatable thread) {
		coordinatedThreads.add(new WeakReference(thread));
	}

	/**
	 * 通知TerminationToken实例:共享该实例的所有可停止线程中的一个线程停止了, 以便其停止其它未被停止的线程。
	 *
	 * @param thread
	 *          已停止的线程
	 */
	protected void notifyThreadTermination(Terminatable thread) {
		WeakReference wrThread;
		Terminatable otherThread;
		while (null != (wrThread = coordinatedThreads.poll())) {
			otherThread = wrThread.get();
			if (null != otherThread && otherThread != thread) {
				otherThread.terminate();
			}
		}
	}

}


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

import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

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(),  new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setDaemon(true);
                return t;
            }

        },new ThreadPoolExecutor.CallerRunsPolicy());

        /*
         * 创建管道线对象
         */
        final SimplePipeline pipeline = new SimplePipeline();

        /*
         * 创建第一条管道
         */
        Pipe pipe = new AbstractPipe()
        {

            @Override
            protected 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()
        {

            @Override
            protected 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()
        {

            @Override
            protected 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()
        {

            @Override
            protected 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()
        {

            @Override
            protected 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;
            }

            @Override
            public void shutdown(long timeout, TimeUnit unit)
            {

                // 在最后一个Pipe中关闭线程池
                //System.out.println("最后一个管道关闭时候队列的大小" + executorSerivce.getQueue().size());
                executorSerivce.shutdown();
                try
                {
                    executorSerivce.awaitTermination(timeout, unit);
                }
                catch (InterruptedException e)
                {
                    ;
                }
            }
        };

        /*
         * 将第五条管道加入管道线
         */
        pipeline.addAsThreadPoolBasedPipe(pipe, executorSerivce);

        /*
         * 管道线初始化
         */
        pipeline.init(pipeline.newDefaultPipelineContext());

        int N = 10;
        try
        {
            for (int i = 0; i < N; i++)
            {
                pipeline.process("Task-" + i);
            }
        }
        catch (IllegalStateException e)
        {
            e.printStackTrace();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        pipeline.shutdown(1, TimeUnit.SECONDS);

    }

}

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


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



我们具体看一下任务四的执行过程:
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]

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

你可能感兴趣的:(设计模式,Java并发编程)