为什么使用线程池?
线程池的优势
-
线程复用,减少线程创建、销毁的开销,提高性能
-
提高响应速度,当任务到达时,无需等待线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资 源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
什么时候用线程池?
-
多个任务操作之间互不影响,或者多个操作需要汇总之类的场景。
-
需要处理的任务数量很大
线程池状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;//线程池容量
// 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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
private static int workerCountOf(int c) { return c & COUNT_MASK; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池源码
execute()
public void execute(Runnable command) {
//健壮性分析
if (command == null)
throw new NullPointerException();
//拿到32位的原子INT 前三位状态 后29位存储工作线程数
int c = ctl.get();
//workerCountOf(c): return c & COUNT_MASK;
if (workerCountOf(c) < corePoolSize) {//获取 工作线程数 <核心线程数
if (addWorker(command, true))
return;
//防止并发不安全
c = ctl.get();
}
//判断线程池是不是running,如果是将任务添加到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//防止并发不安全
int recheck = ctl.get();
//再次判断是否running,若不是 则移除任务
if (! isRunning(recheck) && remove(command))
reject(command);//拒绝策略
//如果线程池处在running但是工作线程为0,
else if (workerCountOf(recheck) == 0)
//阻塞队列有任务,但是没有工作线程,则添加一个空任务工作线程处理阻塞队列里的任务
addWorker(null, false);
}
//创建非核心线程,处理任务
else if (!addWorker(command, false))
reject(command);//拒绝策略
}
阻塞队列类型
addWorker()
private boolean addWorker(Runnable firstTask, boolean core) {
//标记for循环 从内循环 跳出外部for循环
retry://经过大量的判断给工作线程数+1
for (int c = ctl.get();;) {
// runStateAtLeast(int c, int s) : return c >= s;
if (runStateAtLeast(c, SHUTDOWN)//SHUTDOWN: 0 大于或等于则不可能为running后边判断shutdown情况
&& (runStateAtLeast(c, STOP)//c >=1 是stop或者更高的状态
|| firstTask != null //线程任务为不空 联系execute()
//只有是running且阻塞队列有任务但无工作线程时,调用addWorker(null, false);
|| workQueue.isEmpty()))//阻塞队列为空
return false;//构建工作线程失败
for (;;) {
if (workerCountOf(c)//是否大于线程池最大的容量
//判断当前线程是否超过核心线程或者最大线程池容量
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
//将CAS将工作线程数+1
if (compareAndIncrementWorkerCount(c))
break retry;
//若CAS失败 重新获取ctl 可能并发操作了
c = ctl.get(); // Re-read ctl
//重新判断线程池状态,若有变化就重新外侧循环,无变化就内侧循环
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false; //worker开始
boolean workerAdded = false; //worker添加
Worker w = null;//工作线程就是Worker
try {
//创建worker,传入任务
w = new Worker(firstTask);
//从worker中获取线程t
final Thread t = w.thread;
if (t != null) {
//获取线程池的全局锁,避免添加任务时,其它线程将线程池 shutdown
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
//是running状态
if (isRunning(c) ||
//或者 是shutdown状态且线程任务为空
(runStateLessThan(c, STOP) && firstTask == null)) {
//线程是否为运行状态?
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
//将工作线程添加到集合 一个HashSet的集合
workers.add(w);
workerAdded = true;
int s = workers.size();
//如果现在线程工作线程数,大于之前记录的最大工作线程数就替换一下更新最大
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;//返回是否创建工作线程成功
}
Worker的封装
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
//继承AQS 实现runnable接口也就重写了run方法,在线程里的runnable 线程.start 就是调用的worker的run()
}
Worker内部
Worker(Runnable firstTask) {
setState(-1); // AQS状态 禁止中断,直到runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
如何确定线程池大小?
CPU密集型
一个计算为主的程序运行过程中对CPU的占用率很高 。
多线程运行的时候,可以充分利用起所有的CPU核心。但是如果线程数远远超出CPU核心数量则会因为频繁的切换线程而导致任务效率下降。 这个时候尽量压榨CPU,所以对于CPU密集型线程任务,线程池大小=CPU数+1 (4核+1)
Nthreads = Ncpu x Ucpu x (1 + W/C)
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
最优池的大小=CPU数量×目标CPU使用率×(1+等待时间与计算时间比),对于CPU密集型来说等待时间趋近于0,所以应当将线程池大小设为和CPU数量一样,但是为了确保突发情况下若某线程暂停,而CPU周期不会中断工作 通常多设一个。
IO密集型
有大量的网络和文件操作的程序,这样的程序大多数的 时间都是CPU在等在I/O的读写操作,对CPU的使用率不高。这时一个线程处于等待的时候,另一个还可以在CPU里跑。
所以线程池大小可以设为=2倍的CPU数
FixedThreadPool
一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
LinkedBlockingQueue是一个无界队列,就存在当线程池的状态是running时 阻塞队列一直可以接收任务。可能堆积大量的请求,从而导致 OOM。
CachedThreadPool
//底层还是调用ThreadPoolExecutor,不过参数有变化
//corePoolSize 竟然为0,maximumPoolSize为默认的最大值
//当任务队列满时,就会判断maximumPoolSize大小
//keepAliveTime 空闲线程的最大等待时间,,60s后立马销毁线程了
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool创建一个可缓存的线程池,工作线程数量无上限,若长时间没有往线程池提交任务,则空闲线程等待1分钟就会被终止。允许创建的线程数量为Integer.MAX_VALUE
,可能会创建大量线程,从而导致 OOM。