线程池
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
都可以使用线程池
线程池好处/特点
1、重复使用,节约资源,省却了线程的创建和销毁过程
2、响应速度快,不需要创建就可以立刻执行新任务
3、提高线程的可管理性,线程池可以对线程统一调优,分配,监控
线程池作用
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
注意场景
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。
四种线程池的创建方式及效果
1、newCachedThreadPool可自动增减的线程池
//1、newCachedThreadPool无限大小的线程池,jvm可以自动回收线程,线程长度大于任务数会自动回收,线程为0,则创建
ExecutorService newCachedThreadPool=Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp=i;
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("currentThreadName:"+Thread.currentThread().getName()+",i="+temp);
}
});
}
打印信息:
currentThreadName:pool-1-thread-1,i=0
currentThreadName:pool-1-thread-2,i=1
currentThreadName:pool-1-thread-3,i=2
currentThreadName:pool-1-thread-5,i=4
currentThreadName:pool-1-thread-6,i=5
currentThreadName:pool-1-thread-4,i=3
currentThreadName:pool-1-thread-8,i=7
currentThreadName:pool-1-thread-7,i=6
currentThreadName:pool-1-thread-10,i=9
currentThreadName:pool-1-thread-9,i=8
2、newFixedThreadPool定长线程池,控制并发数的线程池
//2、newFixedThreadPool定长线程池,控制并发数量,超出数量的任务则等待
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
final int temp=i;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("currentThreadName:"+Thread.currentThread().getName()+",i="+temp);
}
});
}
打印信息:
currentThreadName:pool-1-thread-2,i=1
currentThreadName:pool-1-thread-3,i=2
currentThreadName:pool-1-thread-2,i=4
currentThreadName:pool-1-thread-2,i=6
currentThreadName:pool-1-thread-2,i=7
currentThreadName:pool-1-thread-1,i=0
currentThreadName:pool-1-thread-1,i=9
currentThreadName:pool-1-thread-2,i=8
currentThreadName:pool-1-thread-3,i=5
currentThreadName:pool-1-thread-4,i=3
3、newScheduledThreadPool计划线程池,可以指定启动时间和周期执行
//3、newScheduledThreadPool,计划线程池,定长线程池,支持定时或周期性执行任务
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
final int temp=i;
newScheduledThreadPool.schedule(new Runnable() {//这里需要使用schedule才能起到定时作用,用execute作用则和普通的定长线程池一样
@Override
public void run() {
System.out.println("currentThreadName:"+Thread.currentThread().getName()+",i="+temp);
}
}, 3, TimeUnit.SECONDS);
}
打印信息:(3秒后执行,除开时间,效果和定长线程池效果相似)
currentThreadName:pool-1-thread-3,i=2
currentThreadName:pool-1-thread-2,i=0
currentThreadName:pool-1-thread-1,i=1
currentThreadName:pool-1-thread-3,i=3
currentThreadName:pool-1-thread-2,i=4
currentThreadName:pool-1-thread-1,i=5
currentThreadName:pool-1-thread-2,i=7
currentThreadName:pool-1-thread-3,i=6
currentThreadName:pool-1-thread-1,i=8
currentThreadName:pool-1-thread-2,i=9
4、newSingleTreadexecutor,单条线程,保证执行顺序
//4、newSingleTreadexecutor,单线程线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int temp=i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("currentThreadName:"+Thread.currentThread().getName()+",i="+temp);
}
});
}
打印信息:
currentThreadName:pool-1-thread-1,i=0
currentThreadName:pool-1-thread-1,i=1
currentThreadName:pool-1-thread-1,i=2
currentThreadName:pool-1-thread-1,i=3
currentThreadName:pool-1-thread-1,i=4
currentThreadName:pool-1-thread-1,i=5
currentThreadName:pool-1-thread-1,i=6
currentThreadName:pool-1-thread-1,i=7
currentThreadName:pool-1-thread-1,i=8
currentThreadName:pool-1-thread-1,i=9
线程池的参数
java.util.concurrent.ThreadPoolExecutor.ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
四种线程池都是调用构造函数ThreadPoolExecutor并传入相应的参数来实现相应效果的,ThreadPoolExecutor构造函数主要有以下5个参数
corePoolSize:
核心线程池大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即创建,而是要等到有任务提交时才会创建,除非调用了prestartCoreThread/prestartAllCoreThreads事先创建核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数(executor.allowCoreThreadTimeOut(true))的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。
maximumPoolSize:
线程池中允许创建的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数(poolSize)小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。
keepAliveTime
如果一个线程处在空闲状态的时间超过了该属性值,就会因为超时而退出。举个例子,如果线程池的核心大小corePoolSize=5,而当前大小poolSize =8,那么超出核心大小的线程,会按照keepAliveTime的值判断是否会超时退出。如果线程池的核心大小corePoolSize=5,而当前大小poolSize =5,那么线程池中所有线程都是核心线程,这个时候线程是否会退出,取决于allowCoreThreadTimeOut。
unit
超时退出时间keepAliveTime的单位。
workQueue
线程池存放超出核心线程池大小corePoolSize数量任务的队列
线程池工作流程
1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获得全局锁);
2、如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue;
3、如果无法将任务加入BlockingQueue(队列已满并且正在运行的线程数量小于 maximumPoolSize),则创建新的线程来处理任务(需要获得全局锁)
4、如果创建新线程将使当前运行的线程超出maxiumPoolSize(队列已满并且正在运行的线程数量大于或等于 maximumPoolSize),任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法(线程池会抛出异常,告诉调用者"我不能再接受任务了");
线程池采取上述的流程进行设计是为了减少获取全局锁的次数。在线程池完成预热(当前运行的线程数大于或等于corePoolSize)之后,几乎所有的excute方法调用都执行步骤2;
5、当一个线程完成任务时,它会从队列中取下一个任务来执行;
6、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小
任务的管理是一件比较容易的事,复杂的是线程的管理,这会涉及线程数量、等待/唤醒、同步/锁、线程创建和死亡等问题。ThreadPoolExecutor与线程相关的几个成员变量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它们共同负责线程的创建和销毁。
其他几个比较重要的参数
poolSize
线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize。
allowCoreThreadTimeOut:
该属性用来控制是否允许核心线程超时退出。If false,core threads stay alive even when idle.If true, core threads use keepAliveTime to time out waiting for work。如果线程池的大小已经达到了corePoolSize,不管有没有任务需要执行,线程池都会保证这些核心线程处于存活状态。可以知道:该属性只是用来控制核心线程的。
largestPoolSize
该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。
合理配置线程池的数量
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当cpu核数+1,这样可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
操作系统之名称解释:
某些进程花费了绝大多数时间在计算上,而其他则在等待I/O上花费了大多是时间,
前者称为计算密集型(CPU密集型)computer-bound,后者称为I/O密集型,I/O-bound。
可以用过以下方法获取CPU核数:
Runtime.getRuntime().availableProcessors()
重入锁,自旋锁,以及CAS锁部分需要查询资料再补齐