Thread Pool(线程池)技术
Thread Pool(线程池)技术技术背景介绍 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些
Thread Pool(线程池)技术
技术背景介绍
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内
存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,
以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可
能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用
已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术
产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的
备注:与线程池会一同出现的概念是数据库连接池,”池化资源”。
在频繁使用线程的场景下,使用线程池是很有必要的可以大大节约服务器资源,
尤其是线程处理的是小任务很快就结束的任务。如果不是频繁使用线程
场景下,并且线程处理的是费时的大任务那么其实不使用线程池也没关系,直接
使用new Thread()来创建线程也没多大影响。
备注:并发数大并且每个任务都是短时间能够完成的,那么这种场景就比较适合使用线程池技术。
Java中线程池实现方案
ThreadPoolExecutor
在jdk1.5之后在java.util.concurrent并发包下ThreadPoolExecutor类可以帮我们实现线程池功能。
ThreadPoolExecutor Code 构造方法(省略了其它三个)
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;
}
备注:其余几个构造方法和这个差不多,只是入参不同罢了。
入参详细解释
corePoolSize
核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建
了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创
建线程去执行任务,除非调用了prestartAllCoreThreads()或者
prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的
意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情
况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一
个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的
任务放到缓存队列当中
maximumPoolSize
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中
的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的
线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如
果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数
不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方
法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起
作用,直到线程池中的线程数为0;
workQueue
工作队列通常有以下几种选择
ArrayBlockingQueue//数组阻塞队列
LinkedBlockingQueue//链表阻塞队列
SynchronousQueue//同步阻塞队列
threadFactory
线程工厂,主要用来创建线程
handler
表示当拒绝处理任务时的策略,有以下四种取值
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
ThreadPoolExecutor的层次结构
public class ThreadPoolExecutor extends AbstractExecutorService {...}
public abstract class AbstractExecutorService implements ExecutorService {...}
public interface ExecutorService extends Executor {...}
public interface Executor {
void execute(Runnable command);//执行方法(最核心)
}
//备注:
//1、Executor执行器接口定义了一个执行任务方法
//2、ExecutorService执行器管理者接口定义了一些额外的管理方法并且还继承了Executor接口
//3、AbstractExecutorService抽象类实现了ExecutorService部分方法
//4、ThreadPoolExecutor继承AbstractExecutorService抽象类并个性化了些方法,供用户使用。
使用Executors来创建线程池
备注:虽然使用ThreadPoolExecutor的构造方法获取线程池对象已经很方便了,但是它那么多入参需要配置让我们很蒙逼所以jdk提供了Executors类,使用其静态方法可以更简单实用线程池技术。
newFixedThreadPool(int nThreads);
newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
ExecutorService newSingleThreadExecutor();
newSingleThreadExecutor(ThreadFactory threadFactory);
newCachedThreadPool();
newCachedThreadPool(ThreadFactory threadFactory);
newSingleThreadScheduledExecutor();
newSingleThreadScheduledExecutor(ThreadFactory threadFactory);
newScheduledThreadPool(int corePoolSize);
newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);
//简直不要太容易
线程池实现原理
线程池状态
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;//
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
先不管移位运算就看英文含义
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够
接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受
新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列
已经清空或执行结束后,线程池被设置为TERMINATED状态。
线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
/**
* Starts a core thread, causing it to idly wait for work.
* 初始化一个核心线程
*/
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
/**
* Starts all core threads, causing them to idly wait for work.
* 初始化所有核心线程
*/
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
任务缓存队列及排队策略
workQueue的类型为BlockingQueue,通常可以取下面三种类型:
1、ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2、LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队
列大小,则默认为Integer.MAX_VALUE;
3、synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直
接新建一个线程来执行新来的任务。
任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
1、shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行
完后才终止,但再也不会接受新的任务
2、shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任
务缓存队列,返回尚未执行的任务
demo 简单使用
public class MainApp {
public static void main(String[] args) {
// ExecutorService executorService = Executors.newFixedThreadPool(3);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 20, 15, TimeUnit.SECONDS,
new ArrayBlockingQueue(5));
for (int i = 1; i <= 20; i++) {
threadPoolExecutor.execute(new TaskThread(i));
System.out.println("已完成的任务数:" + threadPoolExecutor.getCompletedTaskCount());
System.out.println("getLargestPoolSize" + threadPoolExecutor.getLargestPoolSize());
System.out.println("getPoolSize" + threadPoolExecutor.getPoolSize());
System.out.println("--------------------------------");
}
threadPoolExecutor.shutdown();
}
}
任务类 Code
public class TaskThread implements Runnable {
private int i;
public TaskThread(int i) {
this.i = i;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread" + i + "任务执行完毕...");
}
}
备注:配置线程池的线程数是比较麻烦的一件事,通常要考虑很多因素比如任务类型是IO密集型的还是CPU密集型的?以及实际生产环境下的各种限制条件。比较好的办法就试验和测试得出结论。
对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,
通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原
因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。)
如果是IO密集型的任务通常是线程数越多越好(越多当然也就越消耗内存资源),通常可以设置为2N+1.
参考
http://www.infoq.com/cn/articles/java-threadPool/
https://www.ibm.com/developerworks/cn/java/l-threadPool/
http://www.cnblogs.com/dolphin0520/p/3932921.html
https://en.wikipedia.org/wiki/Thread_pool
https://www.ibm.com/developerworks/cn/java/j-jtp0730/
更多推荐
所有评论(0)