设计模式之策略模式

前言

策略模式(Strategy Pattern),定义一系列的算法,将每一种算法封装起来并可以相互替换使用,策略模式让算法独立于使用它的客户端程序而独立变化。通过定义抽象策略接口层,由一系列的不同算法实现出不同的具体策略,客户端程序仅依赖抽象策略接口层,具体处理策略可以由外部传入(本身也含有默认的处理策略)。策略模式在JDK中比较经典的应用是ThreadPoolExecutor线程池中,任务过多时处理策略RejectedExecutionHandler。

场景案例

这里列举几个自己使用到的场景案例,主要列举两个,分别如下:

  1. xmemcached sdk中对象序列化与反序列化处理策略,默认的是jdk自身的序列化与反序列化策略,当然我们也可以实现其提供的抽象策略接口Transcoder来自定义序列化策略,例如通过开源的Kryo,FST等等整体性能都要比jdk自带的序列化机制要好。

  2. jdk中ThreadPoolExecutor线程池中,任务过多时处理策略RejectedExecutionHandler,当线程池中所有线程都处于忙碌状态且任务队列已经塞满的情况下,任务还在持续不断的被提交,那么这时,线程池内部就会启用一个RejectedExecutionHandler策略机制,起到过载保护的作用。下面将重温这4种策略,了解其作用及原理。

    1. ThreadPoolExecutor.AbortPolicy策略
    当继续提交任务时,直接抛出RejectedExecutionException异常,线程池默认处理策略。
    2. ThreadPoolExecutor.CallerRunsPolicy策略
    当继续提交任务时,如果线程池没有shutdown则交由提交该任务的线程执行。
    3. ThreadPoolExecutor.DiscardPolicy策略
    Discard丢弃的意思,即当任务继续提交时,直接丢弃该任务。
    4. ThreadPoolExecutor.DiscardOldestPolicy策略
    当继续提交任务时,丢弃队列中等待最久的任务(即队列中的head任务)并将该任务放入队列中。

上述是jdk自带的4种处理策略,当然我们也可以实现RejectedExecutionHandler该抽象策略接口自定义处理策略;然后在创建线程池时,传入我们自定义的处理策略,这样的设计让软件开发具有更大的灵活性和可扩展性。

示例代码

下面主要是RejectedExecutionHandler各种策略的实现代码及使用示例:

#################################抽象策略接口层###############################
package java.util.concurrent;

public interface RejectedExecutionHandler {

/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}


#################################具体实现策略#################################
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }

/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}


/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {

public AbortPolicy() { }

/**
* Always throws RejectedExecutionException.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}


/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {

public DiscardPolicy() { }

/**
* Does nothing, which has the effect of discarding task r.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}


/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries {@code execute}, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {

public DiscardOldestPolicy() { }

/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}


#################################使用策略#####################################
/**
* @since 1.5
* @author Doug Lea
*/
public class ThreadPoolExecutor extends AbstractExecutorService {

/**
* Handler called when saturated or shutdown in execute.
*/
private volatile RejectedExecutionHandler handler;

/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();

/**
* 可以在构造函数中设置
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

/**
* Sets a new handler for unexecutable tasks.
* @param handler the new handler
* @throws NullPointerException if handler is null
* @see #getRejectedExecutionHandler
*/
public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
if (handler == null)
throw new NullPointerException();
this.handler = handler;
}

/**
* Returns the current handler for unexecutable tasks.
* @return the current handler
* @see #setRejectedExecutionHandler(RejectedExecutionHandler)
*/
public RejectedExecutionHandler getRejectedExecutionHandler() {
return handler;
}
}

软件设计原则

  1. 开闭原则

    当需要替换策略时,我们不用对原有的代码逻辑进行修改,只需要简单实现RejectedExecutionHandler接口扩展出新的策略,然后调整外部策略设置,替换成另外一种处理策略即可(即所谓的扩展开放,修改关闭)。典型的不符合开闭原则的代码,例如核心逻辑中存在大量的if-else逻辑,当有新功能时,就会涉及到调整核心逻辑。

  2. 单一职责原则

    每个处理策略仅关注自身策略的处理逻辑,围绕着任务怎么处理这样一件事,职责清晰分明。

  3. 依赖倒置原则

    线程池依赖抽象接口层, private volatile RejectedExecutionHandler handler;具体的实现策略交由外部传入,或使用默认策略,让软件开发具有更大的灵活性和可扩展性。