并发编程之线程池

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; 
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值