Java构建线程的方式
关于创建线程池究竟有几种方法
有说4种的,那么这四种就是:
- 继承Thread类,重写run方法。
- 实现Runnable接口,重写run方法。
- 实现Callable接口,重新call方法,配置FutureTask执行任务。
- 基于线程池执行任务。
也有的人说是一种,那么就是想表达其余3种都继承了runnable接口,重写了run方法,所以本质上还是一种
为什么需要线程池
线程池优势:
- (1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;.
- (2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
- (3)方便线程并发数的管控。. 因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
- (4)提供更强大的功能,延时定时线程池。
线程池的生命周期
线程池的7个参数
线程池的7个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1.corePoolSize:
线程池核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态,当线程池中的线程数目达到 corePoolSize后,新来的任务将会被添加到缓存队列中,也就是那个workQueue。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定的时长后,核心线程就会被终止。
2.maximumPoolSize:
线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。
3.keepAliveTime:
非核心线程空闲存活时长,超过这个时长,非核心线程就会被回收。这个非核心线程就是上面提到的超过 corePoolSize 后新创建的那些线程,默认情况下,只有当线程池中的线程数大于corePoolSize,且这些"idle Thread"并没有被分配任务时,这个参数才会起作用。另外,如果调用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,在线程池中的线程数不大于corePoolSize,且这些core Thread 也没有被分配任务时,keepAliveTime 参数也会起作用。
当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于非核心线程。
4.unit:参数keepAliveTime的时间单位,共7种取值,在TimeUtil中定义:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5.workQueue:
阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。此队列仅保持由 execute 方法提交的 Runnable 任务。
6.threadFactory:
线程工厂,用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用 Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是 Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。
7.rejectHandler:
拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
- ThreadPoolExecutor.AbortPolicy:
AbortPolicy,抛出RejectedExecutionException异常拒绝任务提交
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- ThreadPoolExecutor.DiscardPolicy:
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- ThreadPoolExecutor.DiscardOldestPolicy:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
- ThreadPoolExecutor.CallerRunsPolicy:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
如何创建线程池
线程池的创建分为两大类方法
- 通过Executors自动创建
- newFixedThreadPool:创建一个固定大小的线程池
- newCachedThreadPool:带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。
- newSingleThreadExecuto:创建单个线程的线程池
- newSingleThreadScheduledExecutor:创建执行定时任务的单个线程的线程池
- newScheduledThreadPool:创建执行定时任务的线程池
- newWorkStealingPool:根据当前设备的配置自动生成线程池
- 通过ThreadPoolExecutor手动创建(推荐使用)
线程池的执行流程(粗略版)
线程池属性标识
// 其实就是int类型,只不过是基于CAS的自增和自减,来保证原子性。
private final AtomicInteger ctl = new AtomicInteger(0);
// ctl保存了线程池的2个信息:
// 1:保存这线程池的状态
// 2:工作线程的个数(核心线程 + 非核心线程)
// 因为int类型占4个字节,一个1字节占8个bit位,高3位维护状态,低29位维护工作线程个数
// 线程池最多可以有多少个工作线程? 占满低位的29个bit位,虽然可以设置Integer.MAX_VALUE,但是达不到
private int ctl = 0;
// 为了方便计算的常量
private static final int COUNT_BITS = 29;
// 工作线程的最大值
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的5个状态
// RUNNING:一切正常,该干活干活!
private static final int RUNNING = -1 << COUNT_BITS;
// SHUTDOWN:线程池正常关闭(公司正常倒闭)
// 新活不接了,之前接到的活,正常干完。 活干完,开掉全部员工!
private static final int SHUTDOWN = 0 << COUNT_BITS;
// STOP:线程池瞬间关闭(公司破产)
// 新活不接了,之前接到的活,也全都不干了。开掉全部员工!
private static final int STOP = 1 << COUNT_BITS;
// TIDYING:公司倒闭前的一些处理(咱们需要去重新terminated的方法)
private static final int TIDYING = 2 << COUNT_BITS;
// TERMINATED:公司已经关门了!
private static final int TERMINATED = 3 << COUNT_BITS;
线程池的execute方法执行
// 将任务提交给线程池处理!!
public void execute(Runnable command) {
// 交给线程池的任务不能为null
if (command == null)
throw new NullPointerException();
// 拿到核心属性,ctl
int c = ctl.get();
// workerCountOf:获取到工作线程个数
// 当前工作线程数 < 核心线程数
if (workerCountOf(c) < corePoolSize) {
// 构建核心线程,并且处理当前任务
// true:核心线程 false:非核心线程
// addWorker返回结果:true-工作线程构建成功 false-构建失败
if (addWorker(command, true))
return;
// 因为有并发情况失败的,ctl可能已经改变了。
c = ctl.get();
}
// 线程池状态是否正常。
// 如果正常,添加任务到工作队列
// workQueue.offer(command),返回true,代表添加任务到工作队列成功
if (isRunning(c) && workQueue.offer(command)) {
// 再次获取ctl,取名为recheck
int recheck = ctl.get();
// 如果任务刚扔到工作队列,线程池就倒闭了,将任务从工作队列移除,如果移除成功,执行拒绝策略
if (!isRunning(recheck) && remove(command)) reject(command);
// 到这都有一种可能,任务已经在工作队列中了。
// 工作线程是0个,添加一个空任务的非核心线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
// 线程池可能会出现没有工作线程,但是工作队列有任务的情况,线程池要避免这种情况
// 上面就是判断这种情况,添加空任务的非核心线程去处理工作队列任务
}
// 任务扔到工作队列失败,添加非核心线程处理当前任务
// 添加成功,返回true,结束。 如果添加失败,返回false,执行拒绝策略
else if (!addWorker(command, false))
// 执行拒绝策略
reject(command);
}
Worker的添加和启动
// 添加工作线程的流程
private boolean addWorker(Runnable firstTask, boolean core) {
// ====================添加前校验==============================
// 在内层循环跳出外层循环
// 外层for循环,在判断线程池状态
// 内存for循环,在判断工作线程个数
heiheihei:
for (;;) {
// 获取ctl
int c = ctl.get();
// 拿到高3位的线程池状态
int rs = runStateOf(c);
// 状态大于SHUTDOWN,说明线程池不是RUNNING
if (rs >= SHUTDOWN &&
// 如果线程池是SHUTDOWN,需要处理掉工作队列中的任务
// 如果是执行addWorker(null, false); 需要将工作线程构建出来
! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
// 如果到这,线程池状态正常
for (;;) {
// 拿到工作线程个数
int wc = workerCountOf(c);
// wc如果超过1 << 29 -1(工作线程最大值,直接告辞)
if (wc >= CAPACITY ||
// 如果是添加核心,比较corePoolSize,如果添加非核心,判断maximumPoolSize
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 将ctl的低29位, + 1,基于CAS的方式,保证原子性!
if (compareAndIncrementWorkerCount(c))
// 跳出外层循环,开始添加工作线程
break heiheihei;
// 如果CAS失败,说明有并发,重新获取ctl
c = ctl.get();
// 如果线程池状态改变了,重新走外层循环。如果没变,不进if,重新走内层循环
if (runStateOf(c) != rs)
continue heiheihei;
}
}
// ====================添加工作线程==============================
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// new一个工作线程
w = new Worker(firstTask);
// 拿到工作线程中的thread对象
final Thread t = w.thread;
// 判断是为了避免你提供的线程工厂有问题!所以做了一个判断
if (t != null) {
// 里面会操作HashSet,HashSet线程不安全,so,加锁
final ReentrantLock mainLock = this.mainLock; mainLock.lock();
try {
// 再次判断线程池状态,如果有问题,直接告辞。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
// 再再次判断,thread对象么调用过start,如果调用了,直接扔异常
if (t.isAlive())
throw new IllegalThreadStateException();
// 工作线程要扔到HashSet里。
workers.add(w);
// 工作线程添加成功
workerAdded = true;
}
} finally {
mainLock.unlock();
} if (workerAdded) {
// 启动工作线程,执行任务~
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) addWorkerFailed(w);
}
return workerStarted;
}