推荐 java多线程系列 ,一气呵成!
使用线程的时候就去创建一个线程(new Thread),这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
所以就有了Java中可以通过线程池来使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。合理的使用线程池能够带来3个很明显的好处:
1.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗2.提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
3.提高线程的可管理性:线程池可以统一管理、分配、调优和监控。
了解线程池得先了解三个类:Executors ,ExecutorService与ThreadPoolExecutor。Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService而ThreadPoolExecutor是ExecutorService的具体实现类。其中Executors 相当于一个创建线程池的工具类。
一.Executors :创建线程池的工具类,在该类中提供了许多静态方法来创建一个线程池,该类中的重要方法如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
newFixedThreadPool(int nThreads) :通过传入的int类型整数创建一个固定大小的线程池,每次提交一个任务就创建一个线程,当线程达到线程池的最大大小后提交的线程会在队列中等待。
newSingleThreadExecutor() :创建一个单线程的线程池。
newCachedThreadPool() :创建一个可以缓存的线程池,当无线程空闲时创建一个新线程,但先前创建的线程空闲时重用先前创建的线程。
newScheduledThreadPool(int corePoolSize) :创建一个可以定时执行或周期性执行的线程池。
二.ThreadPoolExecutor
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
ThreadPoolExecutor重要参数:
- volatile int runState; //当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,用来保证线程之间的可见性。
- static final int RUNNING = 0; //当创建线程池后,初始时,线程池处于RUNNING状态;
- static final int SHUTDOWN = 1; //线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
- static final int STOP = 2; //线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
- static final int TERMINATED = 3; //处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
- private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
- private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
- private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集
- private volatile long keepAliveTime; //线程池空闲时,线程存活的时间
- private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
- private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
- private volatile int maximumPoolSize; //线程池最大能容忍的线程数
- private volatile int poolSize; //线程池中当前的线程数
- private volatile RejectedExecutionHandler handler; //任务拒绝策略
- private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
- private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
- private long completedTaskCount; //用来记录已经执行完毕的任务个数
三.任务的执行
线程池的流程图
step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,如果CorePool内的线程小于CorePoolSize,新创建线程执行任务。
step2.如果当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。
step4.如果线程数大于等于MaxPoolSize,那么执行拒绝策略。(转自点击打开链接 )
四.线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
public boolean prestartCoreThread() {
return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
public int prestartAllCoreThreads() {
int n = 0;
while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
++n;
return n;
}
注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的
r = workQueue.take();
即等待任务队列中有任务。
五.任务缓存队列及排队策略
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。