在应用中,一个线程池工具类,需要些什么,我整理了如下几点:
- 单例,而且是线程安全的单例
- 线程异常处理,当线程在实际执行过程中,如果出现异常,起码得记个log吧
- 线程池的设置,核心线程大小,阻塞队列,最大线程大小
/**
* 线程池工具类
*/
public class ThreadPoolUtils {
//核心线程数
private static final int CORE_POOL_SIZE = 5;
//最大线程数
private static final int MAX_POOL_ZISE = 200;
//存活时间
private static final Long KEEP_ALIVE_TIME = 60L;
//登记式单例
private static class ThreadPoolHolder {
private static final ThreadPoolExecutor executor =
new ThreadPoolExecutor( CORE_POOL_SIZE,
MAX_POOL_ZISE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService getExecutorService(){
return ThreadPoolHolder.executor;
}
/**
* 执行task
*/
public static void execute(Runnable task){
ThreadPoolHolder.executor.execute(getExcpThread(task));
}
public static void execute(Runnable runnable,Thread.UncaughtExceptionHandler excpHandler){
Thread thread = getExcpThread(runnable);
if(excpHandler != null)
thread.setUncaughtExceptionHandler(excpHandler);
ThreadPoolHolder.executor.execute(thread);
}
/**
* 默认线程异常处理器
*/
static class ThreadUncaughtException implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
//log.error....
}
}
/**
* 获取带有异常处理的线程
*/
public static Thread getExcpThread(Runnable task){
Thread taskThread = null;
if(task instanceof Thread)
taskThread = (Thread)task;
else
taskThread = new Thread(task);
if(taskThread.getUncaughtExceptionHandler() == null)
taskThread.setUncaughtExceptionHandler(new ThreadUncaughtException());
return taskThread;
}
}
- 对于单例,这里采用登记式单例,jvm自有的线程安全,而且也是懒加载。当然double check + volatile也可。
- ThreadUncaughtException 默认的异常处理,只里面可以记一些log,报警,或者别的,根据业务而定
- getExcpThread(),一方面,如果线程没有设置异常处理器,则设置一个默认的,如果已经存在,则使用自己设置的。
- 创建线程的时候,有个Executors的静态类可以使用,用它可以快速创建一个线程池。但是建议在实际应用中尽量不要用。
比如:Executors.newCachedThreadPool();看一下源码,它是这么实现的。
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
他的最大线程数为Integer.MAX_VALUE,这个是很恐怖的,而且他采用的是同步队列,如果使用这个线程池的人,不清楚他的问题,随意的添加任务,当业务繁忙的时候很容易出问题。
再看Executors.newFixedThreadPool(),固定大小的线程池,实现源码如下:
new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
这个采用的是无界队列,虽然他的最大线程数和核心线程数都设置为nThreads,但是,最大线程数其实没用,因为核心线程数用完之后,task就会进入阻塞队列,而这个队列是无界的。所以对于固定大小的线程池,如何去设置这个nThreads,很关键,太大了,空闲的时候,可能会有很多空闲的线程。太小了,可能会有很多任务在阻塞队列中,而且这个队列可以一直添加task。