Java线程池实现多消费者消息队列

通常生产者-消费者的经典实现方式是,启动一个消费者线程从阻塞队列里获取消息进行消费,(多个)生产者往队列里插入待消费的数据然后立即返回。如果生产者生产的速率远大于消费者消费的速率,那么队列的待处理数据就会累积得越来越多。

顾名思义,“多消费者”就是开启多个消费者线程,这里借用Java线程池来管理线程的生命周期:

首先,定义一个接口表示异步消费:

import java.util.concurrent.RejectedExecutionException;

/**
 * An object that accepts elements for future consuming.
 *
 * @param  The type of element to be consumed
 */
public interface AsynchronousConsumer {

	/**
     * Accept an element for future consuming.
     *
     * @param e the element to be consumed
     * @return true if accepted, false otherwise
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being accepted for future consuming
     * @throws RejectedExecutionException if the element
     *         cannot be accepted for consuming
     */
	public boolean accept(E e);
}

再定义一个消费者线程(由于线程托管给线程池管理了,这里是定义一个Runnable),多个Runnable通过共用BlockingQueue来实现多个消费者消费。这里 通过指定“batchSize”来表示批量处理(如果值大于1的话),TimeUnit指明了线程(Runnable)的最大空闲时间,超过该时间将会自动退出(如果值为0则表示永远等待)并提供beforeConsume、afterConsume、terminated等方法以便扩展:

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * An abstract {@code Runnable} of consumer for batches consuming Asynchronously
 *
 * @param  The type of element to be consumed
 */
public abstract class ConsumerRunnable implements Runnable, AsynchronousConsumer{
	
	/**
	 * for recording any exception
	 */
	private static final Log log = LogFactory.getLog(ConsumerRunnable.class);
	
	
	/**the elements queue**/
	private final BlockingQueue queue;
	
	/**
	 * the batchSize for {@link #consume} used in every loop
	 */
	private volatile int batchSize;
	
	/**
     * Timeout in nanoseconds for polling an element from the
     * working queue(BlockingQueue).Thread uses this timeout to
     * wait up if the working queue is empty. 
     * zero means wait forever until an element become available.
     */
	private volatile long waitTime;
	
	/**
	 * If true then will cause run() quit
     * when waiting for element if interrupted
	 */
	private volatile boolean quitIfInterrupted = true;
	
	/**
	 * Allocates a new {@code ConsumerRunnable} object
	 * with the given initial parameters and default waitTime(0)
	 * which will cause the thread to wait forever until an element
	 * become available when the element queue is empty.
     * 
	 * @param queue the BlockingQueue for working
	 * @param batchSize the batch size used in {@link #consume} for every loop
	 * 
	 * @throws NullPointerException if the queue is null
	 * @throws IllegalArgumentException if the batchSize is less then or equal to zero
	 */
	public ConsumerRunnable(int batchSize, BlockingQueue queue){
		this.batchSize = PracticalUtils.requirePositive(batchSize);
		this.queue = Objects.requireNonNull(queue);
	}
	
	/**
	 * Allocates a new {@code ConsumerRunnable} object
	 * with the given initial parameters.
	 * A time value of zero will cause the thread to wait forever
     * until an element become available when the element queue is empty.
     * 
	 * @param queue the BlockingQueue for working
	 * @param batchSize the batch size used in {@link #consume} for every loop
	 * @param time the time to wait. A time value of zero will cause
	 * the thread to wait forever until an element become available
	 * when the element queue is empty.
	 * @param unit the time unit of the time argument
	 * 
	 * @throws NullPointerException if any specified parameter is null
	 * @throws IllegalArgumentException if the batchSize is less then or equal to zero,
	 * or the time is less than zero
	 */
	public ConsumerRunnable(int batchSize, long time, TimeUnit unit,
			BlockingQueue queue){
		this.batchSize = PracticalUtils.requirePositive(batchSize);
		this.queue = Objects.requireNonNull(queue);
		setWaitTime(time, unit);
	}
	
	
	/**
     * Accept an element for future consuming.
     * 
     * 

Default implementation is equally invoke * {@link java.util.Queue#offer queue.offer} * * @param e the element to be consumed * @throws NullPointerException if the specified element is null * @throws IllegalArgumentException if some property of this element * prevents it from being accepted for future consuming * @throws RejectedExecutionException if the element * cannot be accepted for future consuming (optional) * @return true if accepted, false otherwise */ public boolean accept(E e){ Objects.requireNonNull(e); return queue.offer(e); } /** * Main worker run loop. Repeatedly gets elements from working queue * and consumes them, while coping with a number of issues: * *

1. Each loop run is preceded by a call to beforeConsume, which * might throw an exception, in which case the {@link #consume} * of the current loop will not be invoked. * *

2. Assuming beforeConsume completes normally, we consume the elements, * gathering any of its thrown exceptions to send to afterConsume. Any thrown * exception in the afterConsume conservatively causes the runnable to quit. * *

3. After {@link #consume} of the current loop completes, we call afterExecute, * which may also throw an exception, which will cause the runnable to quit. * */ @Override public final void run() { starting(); final List list = new ArrayList<>(); try{ while(true){ try { E info; int bSize = batchSize; for(int i=0;i list){ Throwable t = null; try { consume(list); } catch (Throwable t1) { t = t1; throw t1; } finally{ try { afterConsume(list,t); } finally{ list.clear();//clear to be reused for next loop } } } /** * Returns the batchSize for {@link #consume} used in every loop * * @return the batchSize * * @see #setBatchSize */ public int getBatchSize(){ return batchSize; } /** * Sets the batchSize for next loop used in {@link #consume}. * This overrides any value set in the constructor. * * @param batchSize the new batchSize * @throws IllegalArgumentException if the new batchSize is * less than or equal to zero * @see #getBatchSize */ public void setBatchSize(int batchSize){ this.batchSize = PracticalUtils.requirePositive(batchSize); } /** * Returns the element queue used by this thread. Access to the * element queue is intended primarily for debugging and monitoring. * This queue may be in active use. Retrieving the element queue * does not prevent queued element from being saved. * * @return the element queue */ public BlockingQueue getQueue() { return queue; } /** * Sets the time limit for the thread when waiting for * an element to become available. This overrides any value * set in the constructor. zero means wait forever. * Timeout will cause the runnable to quit. * * @param time the time to wait. A time value of zero will cause * the thread to wait forever until an element become available * when the element queue is empty. * @param unit the time unit of the {@code time} argument * @throws IllegalArgumentException if {@code time} less than zero * @see #getWaitTime(TimeUnit) */ public void setWaitTime(long time, TimeUnit unit) { long t = unit.toNanos(PracticalUtils.requireNonNegative(time)); this.waitTime = t; } /** * Returns the thread waiting time, which is the time to wait up * when the working queue is empty. zero means wait forever. * * @param unit the desired time unit of the result * @return the time limit * @see #setWaitTime(long, TimeUnit) */ public long getWaitTime(TimeUnit unit) { return unit.convert(waitTime, TimeUnit.NANOSECONDS); } /** * Returns true if this runnable will quit * if interrupted when waiting for element. * @return true if this runnable will quit * if interrupted when waiting for element */ public boolean isQuitIfInterrupted(){return quitIfInterrupted;} /** * Set quit or not if interrupted when waiting for element. * @param quitIfInterrupted true will cause run() quit * if interrupted when waiting for element */ public void setQuitIfInterrupted(boolean quitIfInterrupted){ this.quitIfInterrupted = quitIfInterrupted; } /* Extension hooks */ /** * Method invoked at the beginning of the run() method. *

Note:if this method throw any exception then * any other method will not be invoked and cause the runnable to quit. */ protected void starting() { } /** * Method invoked prior to current thread waiting for * an element to become available. * Any exception thrown by this method will be ignored. * *

This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.beforeWaiting} at the end of * this method. * */ protected void beforeWait() { } /** * Method invoked prior to invoking {@link #consume} in the * current runnable for every loop. * *

Note:if this method throw any exception then the * {@link #consume} of current loop will not be invoked * *

This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.beforeConsume} at the end of * this method. * * @param list the elements that will be consumed */ protected void beforeConsume(List list) { } /** * For subclass to implement the consume actions. * * @param list the elements that will be consumed */ protected abstract void consume(List list); /** * Method invoked upon one loop completion of {@link #consume} invoked. * This method is invoked by the current thread. If * non-null, the Throwable is the uncaught {@code Error} * that caused execution to terminate abruptly. * *

This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.afterConsume} at the * beginning of this method. * * @param list the elements that has consumed(or to be consumed if the exception caused termination) * @param t the exception that caused termination, or null if * the current loop execution completed normally */ protected void afterConsume(List list, Throwable t) { }; /** * Method invoked when the runnable is about to quit. Default * implementation does nothing. Note: To properly nest multiple * overridings, subclasses should generally invoke * {@code super.terminated} within this method. */ protected void terminated() { }; }

最后需要定义一个线程(这里是Runnable)管理器用于管理线程(Runnable)的创建及消亡:

maximumPoolSize 表示最大的线程(Runnable)数

batchSizeMultiple 用于控制线程的增长,增长条件是“(getPoolSize()=getPoolSize()*batchSize*batchSizeMultiple)”。值为0表示每来一条消息就创建一条线程(Runnable)去处理。

ConsumerRunnableFactory 用于在增长线程(Runnable)时需要创建用来表示线程的ConsumerRunnable实例

ExecutorService 是实际的线程池

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * Manage the instance creation of class
 * {@code ConsumerRunnable} for pooling reuse
 *
 * @param  The type of element to be consumed
 */
public class ThreadPoolConsumer implements AsynchronousConsumer{
	
	/**
	 * The maximum pool size for common use of every business type.
	 */
	public static final int MAX_POOL_SIZE = 20;

	/**
	 * the common pool used for every business type and
	 * can not be shut down!
	 */
	private static final ExecutorService SERVICE;
	
	static{
		ThreadPoolExecutor executor = new ThreadPoolExecutor(
				MAX_POOL_SIZE, MAX_POOL_SIZE, 60L, TimeUnit.SECONDS,
	            new LinkedBlockingQueue(),ThreadFactorys.newFactory("ConsumerRunnable")){
			@Override
			public void execute(Runnable command) {
				BlockingQueue queue = getQueue();
				if(queue.offer(command)){
					int poolSize;
					if(queue.size()>0&&((poolSize=getPoolSize())==0||
							(poolSize<=getActiveCount()&&queue.size()>0))){
						prestartCoreThread();
					}
					return;
				}
				super.execute(command);
			}
		};
		executor.allowCoreThreadTimeOut(true);
		SERVICE = new DelegateExecutorService(executor);;
	}
	
	/**
	 * Returns the ExecutorService used for common in this class,
	 * which can not be shut down and it will throws 
	 * {@link UnsupportedOperationException}
	 * if being invoked
	 * @return the common ExecutorService
	 */
	public static ExecutorService getCommonExecutorService(){
		return SERVICE;
	}


	/**
	 * the pool size, that is ,the instance number of PooledConsumerRunnable
	 * in the pool (ExecutorService {@code esv})
	 */
	private final AtomicInteger counter = new AtomicInteger();
	
	/**
	 * the ExecutorService used for pooling
	 */
	private volatile ExecutorService esv;
	
	/**
	 * the BlockingQueue of elements
	 */
	private final BlockingQueue queue;
	
	/**
	 * the time to wait in nanoseconds. A time value of zero will cause
	 * the thread to wait forever when the working queue is empty.
	 */
	private final long time;
	
	/**
	 * the maximum number of runnables to allow in the
     * pool for current business type
	 */
	private volatile int maxPoolSize;
	
	/**
	 * the batch size used in {@code consume} for every loop
	 */
	private final int batchSize;
	
	/**
	 * the product of batchSize * batchSizeMultiple
	 */
	private volatile int multipleSize;
	
	/**
	 * The multiple used to adjust pool size if needed.
	 */
	private volatile float batchSizeMultiple;
	
	/**
	 * Factory for new ConsumerRunnable. All ConsumerRunnable are created
	 * using this factory (via constructor DelegateConsumerRunnable).
	 */
	private volatile ConsumerRunnableFactory factory;
	
	/**
	 * Allocates a new {@code ThreadPoolConsumer} object
	 * with the given initial parameters and default LinkedBlockingQueue and ExecutorService.
	 * 
	 * @param maximumPoolSize the maximum number of runnables to allow in the
     *        pool for current business type
	 * @param batchSize the batch size used in {@code consume} for every loop
	 * @param batchSizeMultiple the multiple of batchSize, if the condition
	 * {@code (getPoolSize()getPoolSize()*batchSize*batchSizeMultiple)}
	 * is true then we create a new runnable and put it into the ExecutorService
	 * {@code esv}
	 * @param time the time to wait. A time value of zero will cause
	 * the thread to wait forever until an element become available
	 * when the working queue is empty.
	 * @param unit the time unit of the time argument
	 * @throws NullPointerException if specified factory is null
	 * @throws IllegalArgumentException if specified maximumPoolSize or batchSize
	 * is negative
	 */
	public ThreadPoolConsumer(int maximumPoolSize, int batchSize,float batchSizeMultiple,
			long time, TimeUnit unit, ConsumerRunnableFactory factory){
		this(maximumPoolSize, batchSize, batchSizeMultiple, time, unit,
				new LinkedBlockingQueue(), SERVICE, factory);
	}
	
	/**
	 * Allocates a new {@code ThreadPoolConsumer} object
	 * with the given initial parameters.
	 * 
	 * @param maximumPoolSize the maximum number of runnables to allow in the
     *        pool for current business type
	 * @param batchSize the batch size used in {@code consume} for every loop
	 * @param batchSizeMultiple the multiple of batchSize, if the condition
	 * {@code (getPoolSize()getPoolSize()*batchSize*batchSizeMultiple)}
	 * is true then we create a new runnable and put it into the ExecutorService
	 * {@code esv}
	 * @param time the time to wait. A time value of zero will cause
	 * the thread to wait forever until an element become available
	 * when the working queue is empty.
	 * @param unit the time unit of the time argument
	 * @param queue the BlockingQueue for working
	 * @param esv the ExecutorService used for pooling
	 * @throws IllegalArgumentException if specified batchSize is not positive or
	 * the product of (batchSize * batchSizeMultiple), or maximumPoolSize,
	 * or time value is negative
	 * @throws NullPointerException if any specified parameter is null
	 */
	public ThreadPoolConsumer(int maximumPoolSize,int batchSize,float batchSizeMultiple,
			long time, TimeUnit unit,BlockingQueue queue,
			ExecutorService esv, ConsumerRunnableFactory factory) {
		this.esv = Objects.requireNonNull(esv);
		this.queue = Objects.requireNonNull(queue);
		this.batchSize = PracticalUtils.requirePositive(batchSize);
		if(batchSizeMultiple<0){
			throw new IllegalArgumentException("Negative batchSizeMultiple!");
		}
		this.batchSizeMultiple = batchSizeMultiple;
		int mSize = (int) (batchSize * batchSizeMultiple);
		this.multipleSize = PracticalUtils.requireNonNegative(mSize);
		int psize = PracticalUtils.requireNonNegative(maximumPoolSize);
		if(psize>MAX_POOL_SIZE){
			psize = MAX_POOL_SIZE;
		}
		this.maxPoolSize = psize;
		this.time = unit.toNanos(PracticalUtils.requireNonNegative(time));
		this.factory = Objects.requireNonNull(factory);
	}
	
	/**
     * Returns the current number of runnable in the pool.
     *
     * @return the number of runnables
     */
	public int getPoolSize() {
		return counter.get();
	}

	/**
	 * get the ExecutorService instance passed to 
	 * the ThreadPoolConsumer constructor.
	 * if the returned ExecutorService instance is
	 * the common static instance defined in
	 * the ThreadPoolConsumer, it dose'nt
	 * support shutdown() and shutdownNow()
	 * operation which will cause 
	 * {@link UnsupportedOperationException}
	 * if being invoked
	 * 
	 * @return the ExecutorService instance used by
	 * this ThreadPoolConsumer instance
	 */
	public ExecutorService getExecutorService(){
		return esv;
	}
	
	/**
	 * Sets the ExecutorService used to execute the ConsumerRunnable.
	 * @param newExecutorService the new ExecutorService
	 * @throws NullPointerException if newExecutorService is null
	 * @throws IllegalArgumentException if the newExecutorService has
	 * already been shut down
	 */
	public void setExecutorService(ExecutorService newExecutorService){
		if(newExecutorService.isShutdown()){
			throw new IllegalArgumentException();
		}
		this.esv = newExecutorService;
	}
	
	/**
	 * To keep alive when the pool size is zero but the queue is not empty.
	 * @throws IllegalStateException if the executorService has been shut down
	 */
	public void keepAlive(){
		int c;
		if(!queue.isEmpty()&&(c = counter.get())==0){
			c = counter.incrementAndGet();
			if(c==1&&!queue.isEmpty()){
				try {
					DelegateConsumerRunnable r = new DelegateConsumerRunnable();
					esv.execute(r);
				} catch (Throwable t) {
					counter.decrementAndGet();
					t.printStackTrace();
					if(esv.isShutdown()){
						throw new IllegalStateException(
								"The executorService has been shut down!");
					}
					throw t;
				}
			}else{
				counter.decrementAndGet();
			}
			
		}
	}
	
	/**
	 * Method invoked when fails to keep the current pool alive.
	 * 

This implementation does nothing, but may be customized * in subclasses. * @param t the failure reason thrown by {@link #keepAlive()} */ protected void failToKeepAlive(Throwable t){ } private void tryKeepAlive(){ try { keepAlive(); } catch (Throwable t) { failToKeepAlive(t); } } /** * Returns the element queue used by current pool. Access to the * element queue is intended primarily for debugging and monitoring. * This queue may be in active use. Retrieving the element queue * does not prevent queued element from being consumed. * * @return the element queue */ public BlockingQueue getQueue() { return queue; } /** * Accept an element for future consuming. * * @param e the element to be consumed * @return true if accepted, false otherwise * @throws NullPointerException if the specified element is null * @throws IllegalArgumentException if some property of this element * prevents it from being accepted for future consuming * @throws RejectedExecutionException if the element * cannot be accepted for consuming */ public boolean accept(E e){ Objects.requireNonNull(e); int c; boolean added = queue.offer(e); if(added&&((c = counter.get())==0|| (c=c*multipleSize))){ c = counter.incrementAndGet(); if(c==1||(c<=maxPoolSize&&queue.size()>=(c-1)*multipleSize)){ try { DelegateConsumerRunnable r = new DelegateConsumerRunnable(); //If current pool size is larger than one //then except for the first one, the others //don't have to wait too long. if(c>1){ int n = maxPoolSize - c; if(n<=0){ n = 1; } r.setWaitTime(n*100, TimeUnit.MILLISECONDS); } esv.execute(r); } catch (Throwable t) { counter.decrementAndGet(); if(queue.remove(e)){ if(t instanceof RejectedExecutionException){ throw (RejectedExecutionException)t; } throw new RejectedExecutionException(t); }else{ tryKeepAlive(); } } }else{ counter.decrementAndGet(); } } return added; } /** * Adjusts the pool size as needed if the maximumPoolSize has changed * by {@link #setMaximumPoolSize(int) setMaximumPoolSize} or the * {@code batchSizeMultiple} value has changed by * {@link #setBatchSizeMultiple(float) setBatchSizeMultiple}. * @return true if adjusted successfully, false otherwise */ public boolean adjustPoolSize(){ if(queue.isEmpty()){ return false; } int c = counter.get(); if(c==0||(c=c*multipleSize)){ c = counter.incrementAndGet(); if((c==1&&!queue.isEmpty())||(c<=maxPoolSize&&queue.size()>=(c-1)*multipleSize)){ try { DelegateConsumerRunnable r = new DelegateConsumerRunnable(); //If current pool size is larger than one //then except for the first one, the others //don't have to wait too long. if(c>1){ int n = maxPoolSize - c; if(n<=0){ n = 1; } r.setWaitTime(n*100, TimeUnit.MILLISECONDS); } esv.execute(r); return true; } catch (Throwable t) { counter.decrementAndGet(); tryKeepAlive(); } }else{ counter.decrementAndGet(); } } return false; } /** * Sets the {@code ConsumerRunnable} factory used to create new ConsumerRunnables. * * @param factory the new ConsumerRunnable factory * @throws NullPointerException if factory is null * @see #getConsumerRunnableFactory */ public void setConsumerRunnableFactory(ConsumerRunnableFactory factory) { this.factory = Objects.requireNonNull(factory); } /** * Returns the {@code ConsumerRunnable} factory used to create new ConsumerRunnables. * * @return the current ConsumerRunnable factory * @see #setConsumerRunnableFactory(factory) */ public ConsumerRunnableFactory getConsumerRunnableFactory() { return factory; } /** * Sets the maximum allowed number of ConsumerRunnables (threads). * This overrides any value set in the constructor. * * @param maximumPoolSize the new maximum * @throws IllegalArgumentException if the new maximum is * less than or equal to zero * @see #getMaximumPoolSize */ public void setMaximumPoolSize(int maximumPoolSize){ PracticalUtils.requirePositive(maximumPoolSize); if(maxPoolSize != maximumPoolSize){ maxPoolSize = maximumPoolSize; adjustPoolSize(); } } /** * Returns the maximum allowed number of ConsumerRunnables (threads). * * @return the maximum allowed number of ConsumerRunnables (threads) * @see #setMaximumPoolSize */ public int getMaximumPoolSize() { return maxPoolSize; } /** * Returns the multiple of batchSize used when the pool size need to grow up. * * @return the multiple */ public float getBatchSizeMultiple(){ return batchSizeMultiple; } /** * Sets the multiple of batchSize used when the pool size need to grow up. * This overrides any value set in the constructor. * * @param batchSizeMultiple the multiple * @throws IllegalArgumentException if the batchSizeMultiple passed in is negative */ public void setBatchSizeMultiple(float batchSizeMultiple){ if(batchSizeMultiple<0){ throw new IllegalArgumentException("Negative batchSizeMultiple!"); } int mSize = (int) (batchSize * batchSizeMultiple); this.multipleSize = PracticalUtils.requireNonNegative(mSize); this.batchSizeMultiple = batchSizeMultiple; adjustPoolSize(); } /** * A delegate class that dosn'nt support shutdown() and shutdownNow() * */ private static class DelegateExecutorService implements ExecutorService{ private final ExecutorService delegate; private DelegateExecutorService(ExecutorService esv){ delegate = esv; } @Override public void execute(Runnable arg0) { delegate.execute(arg0); } @Override public void shutdown() { throw new UnsupportedOperationException("can not shutdown the common pool!"); } @Override public List shutdownNow() { throw new UnsupportedOperationException("can not shutdown the common pool!"); } @Override public boolean isShutdown() { return false; } @Override public boolean isTerminated() { return false; } @Override public boolean awaitTermination(long arg0, TimeUnit arg2) throws InterruptedException { return false; } @Override public Future submit(Callable arg0) { return delegate.submit(arg0); } @Override public Future submit(Runnable arg0, T arg1) { return delegate.submit(arg0, arg1); } @Override public Future submit(Runnable arg0) { return delegate.submit(arg0); } @Override public List> invokeAll( Collection> arg0) throws InterruptedException { return delegate.invokeAll(arg0); } @Override public List> invokeAll( Collection> arg0, long arg1, TimeUnit arg3) throws InterruptedException { return delegate.invokeAll(arg0, arg1, arg3); } @Override public T invokeAny(Collection> arg0) throws InterruptedException, ExecutionException { return delegate.invokeAny(arg0); } @Override public T invokeAny(Collection> arg0, long arg1, TimeUnit arg3) throws InterruptedException, ExecutionException, TimeoutException { return delegate.invokeAny(arg0, arg1, arg3); } } /** * * Delegate the ConsumerRunnable for pooling manage * */ private final class DelegateConsumerRunnable implements Runnable{ /** * Delegated ConsumerRunnable */ private final ConsumerRunnable r; /** * Allocates a new {@code ConsumerRunnable} object * with the given initial parameter in the constructor * of the outer class and wrap it to {@code DelegateConsumerRunnable}. */ public DelegateConsumerRunnable(){ r = Objects.requireNonNull( factory.newConsumerRunnable( batchSize, time, TimeUnit.NANOSECONDS, queue ) ); } /** * Delegate the method ConsumerRunnable.run() */ @Override public void run() { try { r.run(); } finally{ counter.decrementAndGet(); tryKeepAlive(); } }; /** * Delegate the method ConsumerRunnable.setWaitTime(long time, TimeUnit unit) * @param time the time to wait. A time value of zero will cause the * thread to wait forever until an element become available when the element * queue is empty. * @param unit the time unit of the time argument */ public void setWaitTime(long time, TimeUnit unit){ r.setWaitTime(time, unit); } } }

ConsumerRunnableFactory.java:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 
 * An object that creates new ConsumerRunnable on demand.
 *
 * @param  The type of element to be consumed
 */
public interface ConsumerRunnableFactory {

	/**
	 * Constructs a new {@code ConsumerRunnable} with the given initial parameters.
	 * @param batchSize the batch size used in {@code consume} for every loop
	 * @param time the time to wait. A time value of zero will cause
	 * the thread to wait forever until an element become available
	 * when the working queue is empty.
	 * @param unit the time unit of the time argument
	 * @param queue the BlockingQueue for working
	 * @return constructed ConsumerRunnable
	 */
	ConsumerRunnable newConsumerRunnable(int batchSize, long time, TimeUnit unit,
			BlockingQueue queue);
}

上面定义的ConsumerRunnable.java提供的consume(List list)方法可以进行批处理,这对于数据库的批量插入非常有用(比如用来异步记录用户的登录登出日志,如果同一时间有多条日志需要保存的话,批量保存总比每次保存一条效率要高)。实现原理是,通过扩展自(extends)ConsumerRunnable并使用ThreadLocal来封装数据源对象(这里是org.hibernate.Session)以达到相关目的:

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.Transaction;


/**
 * An abstract {@code Runnable} of consumer for batches saving the elements to DB Asynchronously
 *
 * @param  The type of element to be saved
 */
public abstract class BatchSaveRunnable extends ConsumerRunnable{
	
	/**
	 * for recording any exception
	 */
	private static final Log log = LogFactory.getLog(BatchSaveRunnable.class);
	
	/**
	 * When true (default), this runnable will begin transaction automatically
     * by the program before the method {@code batchSave} and after
     * invoked, the method commit/rollback will also be invoked automatically
     * which means user code donsen't have to call them explicitly.
	 */
	private volatile boolean autoBeginTransaction = true;
	
	/**
	 * The session stored in thread local variable
	 */
	private static final ThreadLocal localSession = new ThreadLocal<>();
	
	private static void clearThreadlocalSession(){
		localSession.remove();
	}
	
	private static void setThreadlocalSession(Session session){
		localSession.set(session);
	}
	
	/**
	 * Get the DB Session from the thread local if any.
	 * @return org.hibernate.Session If it exists in the
	 * thread local then return it, otherwise return null.
	 * The return Session dose'nt have any Transactions,
	 * if you need the transactions you should invoke
	 * {@code session.beginTransaction()} explicitly
	 * and then commit or rollback it by yourself.
	 */
	public static Session getSessionFromThreadlocal(){
		Session session = localSession.get();
		if(session==null){
			//If called in ended() then we don't have any chance to clear thread-local session,
			//so just clear it when return null to avoid memory leak.
			clearThreadlocalSession();
		}
		return session;
	}
	
	/**
	 * Allocates a new {@code ConsumerRunnable} object
	 * with the given initial parameters and default waitTime(0)
	 * which will cause the thread to wait forever until an element
	 * become available when the element queue is empty.
     * 
	 * @param queue the BlockingQueue for working
	 * @param batchSize the batch size used in {@link #batchSave} for every loop
	 * 
	 * @throws NullPointerException if the queue is null
	 * @throws IllegalArgumentException if the batchSize is less then or equal to zero,
	 * or the time is less than zero
	 */
	public BatchSaveRunnable(int batchSize, BlockingQueue queue){
		super(batchSize, queue);
	}
	
	/**
	 * Allocates a new {@code ConsumerRunnable} object
	 * with the given initial parameters.
	 * A time value of zero will cause the thread to wait forever
     * until an element become available when the element queue is empty.
     * 
	 * @param queue the BlockingQueue for working
	 * @param batchSize the batch size used in {@link #batchSave} for every loop
	 * @param time the time to wait. A time value of zero will cause
	 * the thread to wait forever when the element queue is empty.
	 * @param unit the time unit of the time argument
	 * 
	 * @throws NullPointerException if the queue is null
	 * @throws IllegalArgumentException if the batchSize is less then or equal to zero,
	 * or the time is less than zero
	 */
	public BatchSaveRunnable(int batchSize, long time, TimeUnit unit,
			BlockingQueue queue){
		super(batchSize, time, unit, queue);
	}
	
	/**
	 * If the {@code Session} currently used is present in the thread local
	 * then return it otherwise return null.
	 * 

The return Session dose'nt have any Transactions, if you need the * transactions you should invoke session.beginTransaction() explicitly * and then commit or rollback it by yourself. * @return current session if any or null otherwise * @see #getSessionFromThreadlocal() */ protected final Session getCurrentSessionIfPresent(){ return getSessionFromThreadlocal(); } /** * Returns true (the default) if this runnable will begin transaction * automatically by the program before the method {@code batchSave} and * after invoked, the method commit/rollback will also be invoked automatically * which means you don't have to call them explicitly. * * @return {@code true} if this runnable will manage the transactions * automatically, else {@code false} */ public boolean autoBeginTransaction(){ return autoBeginTransaction; } /** * Sets the policy governing whether this runnable should manage * the transactions automatically. * If true (the default), this runnable will begin transaction automatically * by the program before the method {@code batchSave} and after * invoked, the method commit/rollback will also be invoked automatically * which means you don't have to call them explicitly. * * @param auto {@code true} if should manage the transactions * automatically, else {@code false} * */ public void autoBeginTransaction(boolean auto){ autoBeginTransaction = auto; } /** * Saves elements and ensure that the method * {@code afterSave} must be invoked in the end. * @param session the current session * @param list the elements that will be saved to DB */ private void doBatchSave(Session session, List list){ Throwable t = null; Transaction transaction = null; boolean needTransaction = autoBeginTransaction; try { if(needTransaction){ transaction = DBUtil.beginTransaction(session); } batchSave(session, list); if(needTransaction){ DBUtil.commit(transaction); } } catch (Throwable t1) { t = t1; try { if(needTransaction){ DBUtil.rollback(transaction); } if(!session.isOpen()){ clearThreadlocalSession(); } } catch (Throwable e) { clearThreadlocalSession(); DBUtil.closeSession(session); log.error("rollbacking batchSave occurred the exception:", e); } log.error("The runnable["+getClass().getName()+"] which invoked batchSave occurred the exception:" +t1.toString(), t1); throw t1; } finally{ afterSave(list,t); } } private void cleaningUp(){ Session session = getSessionFromThreadlocal(); clearThreadlocalSession(); if(session!=null){ DBUtil.closeSession(session); } } /* overridings */ /** * cleaning up before waiting. *

Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.beforeWaiting} at the end of * this method. */ @Override protected void beforeWait() { cleaningUp(); } /** * ensure have a session */ @Override protected final void beforeConsume(List list) { ensureHasThreadlocalSession(); } private static Session ensureHasThreadlocalSession(){ Session session = getSessionFromThreadlocal(); if(session==null||!session.isOpen()){ session = DBUtil.newSession(); assert session!=null&&session.isOpen(); setThreadlocalSession(session); } return session; } /** * perform batchSave */ @Override protected final void consume(List list) { beforeSave(list); Session session = ensureHasThreadlocalSession(); doBatchSave(session, list); } /** * Subclass should use {@link #afterSave} instead * for extension which is more readable for current class. */ @Override protected final void afterConsume(List list, Throwable t) { //afterSave(list, t); //We can't do this because this method will also be invoked //if beforeSave throws any exception } /** * Cleaning up resources before runnable quit. * Subclass should use {@link #ended} * instead for extension. */ @Override protected final void terminated() { try { cleaningUp(); } finally{ ended(); } }; /* Extension hooks */ /** * Method invoked prior to invoking {@link #batchSave} in the * current runnable for every loop. * *

Note:if this method throw any exception then the * {@link #batchSave} of current loop will not be invoked * *

This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.beforeSave} at the end of * this method. * * @param list the elements that will be saved */ protected void beforeSave(List list) { } /** * for subclass to implement. * *

Note:subclass doesn't required to invoke commit/rollback explicitly * by default, unless the default policy changed by invoked * {@link #autoBeginTransaction(boolean) autoBeginTransaction(boolean)} * @param session the DB session currently used * @param list the elements that to be saved */ protected abstract void batchSave(Session session, List list); /** * Method invoked upon one loop completion of {@link #batchSave} invoked. * This method is invoked by the current thread. If * non-null, the Throwable is the uncaught {@code Error} * that caused execution to terminate abruptly. * *

This implementation does nothing, but may be customized in * subclasses. Note: To properly nest multiple overridings, subclasses * should generally invoke {@code super.afterSave} at the * beginning of this method. * * @param list the elements that has saved(or to be saved if the exception caused termination) * @param t the exception that caused termination, or null if * the current loop execution completed normally */ protected void afterSave(List list, Throwable t) { }; /** * Method invoked when the runnable is about to quit. Default * implementation does nothing. Note: To properly nest multiple * overridings, subclasses should generally invoke * {@code super.ended} within this method. */ protected void ended(){}; }

通过重写父类的beforeWait()方法在等待之前释放掉数据库连接,并提供了beforeSave、afterSave等语义更明确的可扩展的方法。

其他涉及到的类:

DefaultBatchSaveRunnableFactory.java:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * An implementation of ConsumerRunnableFactory for
 * {@code BatchSaveRunnable} which will simply implement
 * {@code BatchSaveRunnable} by save elements directly :
 * 
 {@code  @Override
 * protected void batchSave(Session session, List{@literal } list) {
 *	for(E e:list){
 *	   session.save(e);
 *	}
 * }
 * 
* * @param The type of element to be saved */ public class DefaultBatchSaveRunnableFactory implements ConsumerRunnableFactory{ @Override public ConsumerRunnable newConsumerRunnable(int batchSize, long time, TimeUnit unit, BlockingQueue queue) { return new SimpleBatchSaveRunnable(batchSize, time, unit, queue); } }

SimpleBatchSaveRunnable.java:

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import org.hibernate.Session;


/**
 * A simple implementation of {@code BatchSaveRunnable} :
 * 
 {@code  @Override
 * protected void batchSave(Session session, List{@literal } list) {
 *	for(E e:list){
 *	   session.save(e);
 *	}
 * }
 * 
* * @param The type of element to be saved */ public class SimpleBatchSaveRunnable extends BatchSaveRunnable{ public SimpleBatchSaveRunnable(int batchSize, long time, TimeUnit unit, BlockingQueue queue) { super(batchSize, time, unit, queue); } @Override protected void batchSave(Session session, List list) { DBUtil.batchSave(session, list); } }

DBUtil.java:

import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.Transaction;


public final class DBUtil {

	private static final Log log = LogFactory.getLog(DBUtil.class);
	
	private DBUtil(){}
	
	/**
	 * Force the underlying transaction to roll back.
	 * @param transaction
	 */
	public static void rollback(Transaction transaction) {
		if ((transaction != null) && (transaction.isActive())) {
			log.warn(" rollbacking the transaction...");
			transaction.rollback();
		}
	}
	
	public static void commit(Transaction transaction){
		if(transaction!=null&&transaction.isActive()){
			log.warn(" committing the transaction...");
			transaction.commit();
		}
	}
	
	public static void commitAndClose(Session session){
		if (session != null && session.isOpen()){
			commit(session.getTransaction());
			closeSession(session);
		}
	}
	
	public static Transaction beginTransaction(Session session){
		log.warn(" begining the transaction...");
		return session.beginTransaction();
	}
	
	public static void closeSession(Session session){
		if(session!=null){
			try {
				if(session.isOpen()){
					log.warn(" closing the session...");
					session.close();
				}
			} catch (Throwable t) {
				log.error("An error has occurred when closing the db session!", t);
			}
		}
	}
	
	public static Session newSession(){
		log.warn(" opening a new session...");
		//Opens a new session
		//TODO just return null as example
		return null;
	}
	
	/**
	 * roll back transaction and then close the session
	 * @param session
	 */
	public static void rollbackAndClose(Session session) {
		try {
			if (session != null && session.isOpen()) {
				Transaction transaction = session.getTransaction();
				rollback(transaction);
				closeSession(session);
			}
		} catch (Throwable rbEx) {
			log.error(" An error has occurred when rollbacking the transaction!", rbEx);
		} 
	}
	
	/**
	 * Batch save elements:
	 * 
 {@code  @Override
	 * protected void batchSave(Session session, Collection{@literal } col) {
	 *	for(E e:col){
	 *	   session.save(e);
	 *	}
	 * }
	 * 
* @param session DB Session * @param col the elements to be saved * @param E The type of element to be saved */ public static void batchSave(Session session, Collection col){ if(col==null||col.isEmpty()){ return; } for(E e:col){ session.save(e); } } }

PracticalUtils.java:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


public final class PracticalUtils {
	
	private PracticalUtils(){}

	/**
     * 

Checks if a String is empty ("") or null.

*
     * StringUtils.isEmpty(null)      = true
     * StringUtils.isEmpty("")        = true
     * StringUtils.isEmpty(" ")       = false
     * StringUtils.isEmpty("bob")     = false
     * StringUtils.isEmpty("  bob  ") = false
     * 
* @param str the String to check, may be null * @return true if the String is empty or null */ public static boolean isEmpty(String str) { return str == null || str.length() == 0; } /** * Checks if there is empty string * @param ss the strings to be checked * @return true if there is any empty string in specified parameters, false otherwise */ public static boolean hasEmpty(String... ss){ if(ss==null){ return true; } for(String s:ss){ if(s==null||s.isEmpty()){ return true; } } return false; } /** * 判断是否存在null对象 * @param objs 那些需要检查的对象 * @return true 存在,false 不存在 */ @SafeVarargs public static boolean hasNull(T... objs){ if(objs==null) return true; for(Object obj:objs){ if(obj==null) return true; } return false; } /** * 如果参数obj为null则返回空字符串"",否则返回obj.toString * @param obj * @return */ public static String nullToEmpty(Object obj){ if(obj==null) return ""; return obj.toString(); } /** * 1. 获取参数obj的longValue,如果obj是Number的实例的话。
* 2. 如果obj是String实例的话 尝试调用Long.parseLong,失败则返回0。 * @param obj * @return obj.longValue or 0 if parse failed */ public static long getLongVal(Object obj){ long val = 0; if(obj==null){ return val; } if(obj instanceof Number){ val = ((Number)obj).longValue(); }else if(obj instanceof String){ String s = (String)obj; if(s.isEmpty()){ return val; } try { val = Long.parseLong(s); } catch (NumberFormatException ignore) {} } return val; } /** * 1. 获取参数obj的intValue,如果obj是Number的实例的话。
* 2. 如果obj是String实例的话 尝试调用Integer.parseInt,失败则返回0。 * @param obj * @return obj.intValue or 0 if parse failed */ public static int getIntVal(Object obj){ int val = 0; if(obj==null){ return val; } if(obj instanceof Number){ val = ((Number)obj).intValue(); }else if(obj instanceof String){ String s = (String)obj; if(s.isEmpty()){ return val; } try { val = Integer.parseInt(s); } catch (NumberFormatException ignore) {} } return val; } /** * 以逗号分隔的long字符串转为List * @param strDigits * @return * @throws IllegalArgumentException 如果其中的字符无法转换为long类型 */ public static List getLongsBySplitComma(String strDigits) { if(isEmpty(strDigits)) return Collections.emptyList(); String[] digis = strDigits.split(","); List list = new ArrayList<>(digis.length); for(String digi:digis){ try { list.add(Long.valueOf(digi)); } catch (Exception e) { throw new IllegalArgumentException("["+digi+"]无法转换成long类型"); } } return list; } /** * 以逗号分隔的int字符串转为List * @param strDigits * @return * @throws IllegalArgumentException 如果其中的字符无法转换为long类型 */ public static List getIntsBySplitComma(String strDigits) { if(isEmpty(strDigits)) return Collections.emptyList(); String[] digis = strDigits.split(","); List list = new ArrayList<>(digis.length); for(String digi:digis){ try { list.add(Integer.valueOf(digi)); } catch (Exception e) { throw new IllegalArgumentException("["+digi+"]无法转换成int类型"); } } return list; } /** * 获取参数obj对应的boolean值 *
1. 如果obj是Number的实例的话,只有Number.intValue==1才返回true *
2. 如果obj是Boolean的实例的话,直接返回该Boolean *
3. 如果obj是String的实例的话,只有是"true"(不区分大小写)或"1"才返回true *
3. 如果obj是Character的实例的话,只有Character.charValue=='1'才返回true *
否则返回false * @param obj * @return */ public static boolean getBooleanVal(Object obj){ if(obj instanceof Boolean){ return ((Boolean)obj); } if(obj instanceof String){ String s = ((String)obj); return s.equals("1")||s.equalsIgnoreCase("true"); } if(obj instanceof Number){ return ((Number)obj).intValue()==1; } if(obj instanceof Character){ return ((Character)obj).charValue()=='1'; } return false; } /** * 将JavaBean对象的属性-值转为Map形式,包括继承的protected、public属性。
* 优先调用属性的getXXX方法获取值 * @param javaBean 需要将属性-值转为Map的JavaBean实例 * @return 包含属性-值的Map对象,key为属性名,value为属性值 * @throws Exception */ public static Map beanToMap(Object javaBean) throws Exception{ final Collection excludeField = null; return beanToMap(javaBean, excludeField); } /** * 将JavaBean对象的属性-值转为Map形式,包括继承的protected、public属性。
* 优先调用属性的getXXX方法获取值 * @param javaBean javaBean 需要将属性-值转为Map的JavaBean实例 * @param excludeField 需要忽略的属性名 * @return 包含属性-值的Map对象,key为属性名,value为属性值 * @throws Exception */ public static Map beanToMap(Object javaBean,Collection excludeField) throws Exception{ Map map = new HashMap<>(); if(javaBean==null) return map; Set excludeFields = null; if(excludeField!=null&&!excludeField.isEmpty()){ if(excludeField instanceof Set){ excludeFields = (Set) excludeField; }else{ int size = excludeField.size()<<1; if(size<0)//overflow size = excludeField.size(); excludeFields = new HashSet<>(size); excludeFields.addAll(excludeField); } } Class cls = javaBean.getClass(); superClassToMap(javaBean, cls.getSuperclass(), map, excludeFields); beanToMap(javaBean, cls, map, false, excludeFields); return map; } /** * 将JavaBean对象的属性-值转为Map形式,包括继承的protected、public属性。
* 优先调用属性的getXXX方法获取值 * @param javaBeans 需要将属性-值转为Map的JavaBean实例集合 * @param excludeField 需要忽略的属性名 * @return 包含属性-值的Map对象集合,key为属性名,value为属性值 * @throws Exception */ public static List> beansToListMap(Collection javaBeans, Collection excludeField) throws Exception{ if(javaBeans==null||javaBeans.isEmpty()) return new ArrayList<>(0); List> list = new ArrayList<>(javaBeans.size()); for(Object javaBean:javaBeans){ list.add(beanToMap(javaBean, excludeField)); } return list; } /** * 将JavaBean对象的属性-值转为Map形式,包括继承的protected、public属性。
* 优先调用属性的getXXX方法获取值 * @param javaBeans 需要将属性-值转为Map的JavaBean实例集合 * @return 包含属性-值的Map对象集合,key为属性名,value为属性值 * @throws Exception */ public static List> beansToListMap(Collection javaBeans) throws Exception{ final Collection excludeField = null; return beansToListMap(javaBeans, excludeField); } private static void beanToMap(Object javaBean,Class cls,Map resultMap, boolean isSuper,Set excludeField) throws Exception{ Field[] fields = cls.getDeclaredFields(); if(fields.length>0){ Map methodMap = getGetterSetterMethods(cls, true); for(Field field:fields){ if(isSuper){ int modifiers = field.getModifiers(); if(!Modifier.isPublic(modifiers)&&!Modifier.isProtected(modifiers)) continue; } String name = field.getName(); if(excludeField!=null&&excludeField.contains(name)) continue; Method method = methodMap.get(name); if(method!=null){ if(!method.isAccessible()){ method.setAccessible(true); } resultMap.put(name, method.invoke(javaBean)); }else{ if(!field.isAccessible()){ field.setAccessible(true); } Object value = field.get(javaBean); resultMap.put(name, value); } } } } private static void superClassToMap(Object javaBean,Class superClass, Map resultMap,Set excludeField) throws Exception{ if(superClass!=null){ superClassToMap(javaBean,superClass.getSuperclass(), resultMap, excludeField); beanToMap(javaBean, superClass, resultMap, true, excludeField); } } private static String getFieldName(String getMethodName){ int len = getMethodName.length(); if(len>4){ StringBuilder sb = new StringBuilder(len-3); sb.append(Character.toLowerCase(getMethodName.charAt(3))) .append(getMethodName.substring(4)); return sb.toString(); }else{ return getMethodName.substring(3).toLowerCase(); } } /** * 根据包含对象属性-值的Map创建对应的JavaBean实例,该JavaBean须有相应的无参构造方法 * @param beanMap 包含对象属性-值的Map * @param beanClass 需要创建JavaBean实例对应的Class * @return * @throws Exception */ public static T mapToBean(Map beanMap,Class beanClass) throws Exception{ Constructor[] constructors = beanClass.getDeclaredConstructors(); T bean = null; for(Constructor constructor:constructors){ if(constructor.getParameterCount()==0){ if(!constructor.isAccessible()) constructor.setAccessible(true); bean = (T) constructor.newInstance(); break; } } if(bean==null){ throw new InstantiationException("there no any non-arg constructor to be invoked in the class "+beanClass.getTypeName()); } mapToBean(bean, beanMap, beanClass); return bean; } private static void mapToBean(T bean,Map beanMap,Class beanClass) throws Exception{ if(beanClass!=null){ mapToBean(bean, beanMap, beanClass.getSuperclass()); Map methodMap = getGetterSetterMethods(beanClass, false); for(Entry entry:beanMap.entrySet()){ String key = entry.getKey(); Object val = entry.getValue(); Method method = methodMap.get(key); if(method!=null){ if(!method.isAccessible()) method.setAccessible(true); method.invoke(bean, val); } } } } private static Map getGetterSetterMethods(Class cls,boolean retrieveGetterMethods){ Method[] methods = cls.getDeclaredMethods(); int methodLength = methods.length; Map methodMap = new HashMap<>(methodLength<<1); if(methodLength>0){ for(Method method:methods){ String name = method.getName(); if(name.length()>3){ int parameterCount = method.getParameterCount(); if(retrieveGetterMethods){ if(parameterCount>0||!name.startsWith("get")) continue; }else{ if(parameterCount!=1||!name.startsWith("set")) continue; } int modifiers = method.getModifiers(); if(!Modifier.isPublic(modifiers)&&!Modifier.isProtected(modifiers)) continue; String fieldName = getFieldName(name); methodMap.put(fieldName, method); } } } return methodMap; } /** * 将参数连接起来,忽略null * @param strs * @return 返回结果为连接参数产生的字符串 */ public static String concat(String... strs){ return concat_ws("", strs); } /** * contcat_ws() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符 * @param separator * @param strs * @return 返回结果为连接参数产生的字符串 */ public static String concat_ws(String separator,String... strs){ if(strs==null){ return ""; } StringBuilder sb = new StringBuilder(); if(separator==null||separator.length()==0){ for(String s:strs){ if(s==null){ continue; } sb.append(s); } return sb.toString(); } for(int i=0,len=strs.length;i String concat(String separator,Collection coll){ if(coll==null||coll.isEmpty()){ return ""; } StringBuilder sb = new StringBuilder(); if(separator==null||separator.length()==0){ for(E e:coll){ if(e==null){ continue; } sb.append(e); } return sb.toString(); } int i = 0; for(E e:coll){ if(e==null){ continue; } if(i++!=0){ sb.append(separator); } sb.append(e); } return sb.toString(); } /** * 将集合内容按英文逗号连接起来。忽略null * @param coll 集合内容 * @return 连接后的字符串 */ public static String concatWithComma(Collection coll){ return concat(",", coll); } /** * 判断指定字符是否是ASCII字符-0~9 * @param c 需要判断的字符 * @return true 是 false 否 */ public static boolean isAsciiDigit(char c){ return c>='0'&&c<='9'; } /** * 判断指定字符是否是ASCII字符-A~Z * @param c 需要判断的字符 * @return true 是 false 否 */ public static boolean isAsciiUpperCase(char c){ return c>='A'&&c<='Z'; } /** * 判断指定字符是否是ASCII字符-a~z * @param c 需要判断的字符 * @return true 是 false 否 */ public static boolean isAsciiLowerCase(char c){ return c>='a'&&c<='z'; } /** * 判断指定字符是否是ASCII字符-[a~z||A~Z] * @param c 需要判断的字符 * @return true 是 false 否 */ public static boolean isAsciiLetter(char c){ return isAsciiUpperCase(c)||isAsciiLowerCase(c); } /** * 判断指定字符是否是ASCII字符-[0~9||a~z||A~Z] * @param c 需要判断的字符 * @return true 是 false 否 */ public static boolean isAsciiLetterOrDigit(char c){ return isAsciiDigit(c)||isAsciiLetter(c); } /** * Checks whether the specified parameter is non-negative * * @param num the parameter to check for non-negative * @return the specified parameter if non-negative * * @throws IllegalArgumentException if the specified parameter is negative */ public static int requireNonNegative(int num){ if(num<0){ throw new IllegalArgumentException("Negative parameter!"); } return num; } /** * Checks whether the specified parameter is non-negative * * @param num the parameter to check for non-negative * @return the specified parameter if non-negative * * @throws IllegalArgumentException if the specified parameter is negative */ public static long requireNonNegative(long num){ if(num<0){ throw new IllegalArgumentException("Negative parameter!"); } return num; } /** * Checks whether the specified parameter is positive * * @param num the parameter to check for positive * @return the specified parameter if positive * * @throws IllegalArgumentException if the specified parameter is negative or zero */ public static int requirePositive(int num){ if(num<=0){ throw new IllegalArgumentException("Non positive parameter!"); } return num; } /** * Checks whether the specified parameter is positive * * @param num the parameter to check for positive * @return the specified parameter if positive * * @throws IllegalArgumentException if the specified parameter is negative or zero */ public static long requirePositive(long num){ if(num<=0){ throw new IllegalArgumentException("Non positive parameter!"); } return num; } }

ThreadFactorys.java:

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * A utility class to offer an implementation of {@code ThreadFactory}
 * conveniently which is the same as 
 * {@code java.util.concurrent.Executors$DefaultThreadFactory}
 * but will offer a more specific thread name by the specified
 * parameter {@code threadNamePrefix}
 *
 */
public final class ThreadFactorys {

	private ThreadFactorys(){}
	
	/**
	 * Creates a new {@code ThreadFactory} with specified thread name prefix.
	 * The thread name will be "{@code threadNamePrefix-PoolThread-n}",
	 * where {@code threadNamePrefix} is the parameter passed in, and the 
	 * {@code n} is the number of threads that have been created via this ThreadFactory.
	 * @param threadNamePrefix the thread name prefix
	 * @return a new {@code ThreadFactory}
	 */
	public static ThreadFactory newFactory(String threadNamePrefix){
		//Copy from java.util.concurrent.Executors$DefaultThreadFactory
		//to offer a more specific thread name
		return new ThreadFactory(){

			private final AtomicInteger threadNumber = new AtomicInteger(1);
			private final String namePrefix = threadNamePrefix+"-PoolThread-";
			
			@Override
			public Thread newThread(Runnable r) {
	            Thread t = new Thread(r, 
	            		namePrefix + threadNumber.getAndIncrement());
	            if (t.isDaemon())
	                t.setDaemon(false);
	            if (t.getPriority() != Thread.NORM_PRIORITY)
	                t.setPriority(Thread.NORM_PRIORITY);
	            return t;
			}
			
		};
	}
}

以上只是“多生产者-多消费者”的一个简单实现,并没有什么容灾机制(比如系统意外退出时,并没有将队列里尚未消费的数据进行持久化导致消息丢失)、回调机制等,可用于异步处理一些无关紧要的事情(比如上面提到的用于异步记录用户登录登出日志,用于即使稍微有记录丢失也无关紧要的业务场景)。

你可能感兴趣的:(原创)