什么是池化技术
常见的池化技术有:连接池、对象池、内存池、线程池等。池化技术的核心是复用。
线程池的概念
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。使用线程池可以很好的提高性能,尤其是程序中需要创建大量生存期很短暂的线程。
线程池的优势
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁的开销。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
线程池的流程
- 如果当前woker数量小于corePoolSize,则新建一个woker并把当前任务分配给该woker线程,成功则返回。
- 如果第一步失败,则尝试把任务放入阻塞队列,如果成功则返回。
- 如果第二步失败,则判断如果当前woker数量小于maximumPoolSize,则新建一个woker并把当前任务分配给该woker线程,成功则返回。
- 如果第三步失败,则调用拒绝策略处理该任务。
线程池实现的思路
提前创建一系列的线程,保存在这个线程池中。有任务要执行的时候,从线程池中取出线程来执行。没有任务的时候,把线程放回到线程池中去。
核心源码实现
线程池的本质就是使用了一个线程安全的工作队列连接工作 者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断地从工作队列上取出工作并执行。
当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务之后会通知任意一个工作者线程,随着大量的任务被提交,更多的工作者线程会被唤醒。
注意的是,核心线程在完成任务后不会被销毁,而是在循环getTask()时被阻塞队列阻塞住。只有当线程数大于了核心线程数的那些普通线程会被销毁。
构造器参数:
corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize,即使有其他空闲线程能够执行新来的任务,也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是*队列,则maximumPoolSize则不起作用,因为无法提交至核心线程池的线程会一直持续地放入workQueue.
keepAliveTime:线程存活时间(当线程池允许线程超时且运行中的线程数量超过corePoolSize时,会按照此变量设置时间关闭线程)
TimeUnit:keepAliveTime的单位
BlockingQueue<Runnable> workQueue:缓冲队列,来不及执行的任务存放的阻塞队列
RejectedExecutionHandler handler:拒绝处理任务类(默认:AbortPolicy 会抛异常)
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
threadFactory:创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory
———————————————————————————————————————————————————————————————————————————————
//构造器
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor.java
private final BlockingQueue<Runnable> workQueue;//缓冲队列
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//原子类用来计数
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//1 当前运行的线程数量小于核心线程数量,直接将任务加入worker启动运行。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//如果失败,则获取最新的线程池数据
c = ctl.get();
}
/*2 运行线程数量大于核心线程数量时,上面的if分支针对大于corePoolSize,并且缓存队列加入任务操作成功的情况。
运行中并且将任务加入缓冲队列成功,正常来说这样已经完成了处理逻辑。
但是为了保险起见,增加了状态出现异常的确认判断,如果状态出现异常会继续remove操作,如果执行true,则按照拒绝处理策略驳回任务;*/
//运行线程数量大于核心线程数量时,如果线程池仍在运行,则把任务放到阻塞队列中等待执行。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//当任务成功放入队列时,如果recheck发现线程池已经不再运行了则从队列中把任务删除
if (! isRunning(recheck) && remove(command))
//删除成功以后,会调用构造参数传入的拒绝策略。
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*3 这里针对运行线程数量超过了corePoolSize,并且缓存队列也已经放满的情况。
注意第二个参数是false,可以在下面addWorker方法看到,就是针对线程池最大线程数量maximumPoolSize的判断。*/
else if (!addWorker(command, false))
//如果基于maximumPoolSize新建woker失败,此时是线程池中线程数已达到上限,队列已满,则调用构造参数中传入的拒绝策略
reject(command);
}
addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
// CAS+死循环实现的关于线程池状态,线程数量的校验与更新逻辑
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//把指定任务作为参数新建一个worker线程
w = new Worker(firstTask);
//变量t就是代表woker线程
final Thread t = w.thread;
if (t != null) {
// 线程池重入锁
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 rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 线程启动,执行任务(Worker.thread(firstTask).start())
// 找到Worker的实现的run方法
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//如果woker启动失败,则进行一些善后工作,比如说修改当前woker数量等等
addWorkerFailed(w);
}
return workerStarted;
}
Worker类
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
//Worker类run方法中调用了runWorker方法
public void run() {
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//task就是Woker构造函数入参指定的任务,即用户提交的任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 先执行firstTask,再从workerQueue中取task(getTask()),一直循环执行
//我们都知道构造参数设置的时间代表了线程池中的线程,即woker线程的存活时间,如果到期则回收woker线程
//这个逻辑的实现就在getTask中。
//来不及执行的任务,线程池会放入一个阻塞队列,getTask方法就是去阻塞队列中取任务,用户设置的存活时间,就是
//从这个阻塞队列中取任务等待的最大时间,如果getTask返回null,意思就是woker等待了指定时间仍然没有
//取到任务,此时就会跳过循环体,进入woker线程的销毁逻辑。
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//该方法是个空的实现,如果有需要用户可以自己继承该类进行实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); //运行传入的线程的run方法
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//当指定任务执行完成,阻塞队列中也取不到可执行任务时,会进入这里,做一些善后工作
//比如在corePoolSize跟maximumPoolSize之间的woker会进行回收
processWorkerExit(w, completedAbruptly);
}
}
getTask()方法
从阻塞任务队列中取任务,如果设置了allowCoreThreadTimeOut(true) 或者当前运行的任务数大于设置的核心线程数,那么timed =true 。此时将使用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)从任务队列中取任务,而如果没有设置,那么使用workQueue.take() 取任务,对于阻塞队列,poll(long timeout, TimeUnit unit) 将会在规定的时间内去任务,如果没取到就返回null。take()会一直阻塞,等待任务的添加。
到此相信我们都能够理解为什么我们的线程池能够一直等待任务的执行而不被销毁了,其实也就是进入了阻塞状态而已。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
//死循环
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?注意,此处决定是否销毁线程
//条件是开启了 allowCoreThreadTimeOut,或者总线程数大于了核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
线程池的使用
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
这四种线程池底层都是依赖ThreadPoolExecutor的构造器生成的。
//定长线程池
//corePoolSize跟maximumPoolSize值一样,同时传入一个*阻塞队列
//该线程池的线程会维持在指定线程数,不会进行回收
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//缓存线程池
//这个线程池corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE
//意思也就是说来一个任务就创建一个woker,回收时间是60s
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);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
//单线程池
//线程池中只有一个线程进行任务执行,其他的都放入阻塞队列
//外面包装的FinalizableDelegatedExecutorService类实现了finalize方法,在JVM垃圾回收的时候会关闭线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用例子:
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//创建一个可缓存线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);//创建一个定长线程池,支持定时及周期性任务执行
ExecutorService single = Executors.newSingleThreadExecutor();//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
for(int i=0;i<10;i++){
final int index = i;
try {
Thread.sleep(index*500);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
//定期3s执行
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3s");
}
}, 3, TimeUnit.SECONDS);
single.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个
future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方
法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线
程一段时间后立即返回,这时候有可能任务没有执行完。
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池
executor.shutdown();
}
线程池设计大小
任务一般分为:CPU密集型、IO密集型
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
估算公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
一般经验上设置大小
1、CPU密集型
尽量使用较小的线程池,一般Cpu核心数+1
因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销
2、IO密集型
方法一:可以使用较大的线程池,一般CPU核心数 * 2
IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间