Java线程池主要用于管理线程组及其运行状态,以便Java虚拟机更好的利用CPU资源,它的主要作用是线程复用,线程资源管理,控制操作系统的最大并发数,以保证系统高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)的运行
一.为什么要用线程池
1、降低资源消耗
我们知道,线程在java堆里面是一个对象,也是操作系统的一部分资源,线程创建、销毁需要时间。 如果创建线程加上销毁的时间大于执行任务的时间就很不划算了。所以,线程池通过重复利用已创建 的线程降低线程创建和销毁造成的消耗。
java对象占用堆内存,操作系统线程占用系统的内存,根据JVM虚拟机的规范,一个线程默认最大的是1M,
我们可以通过-xss去修改,这个栈空间是需要内存分配的,线程的增加,必然增加内存的消耗,所以线程不是越多越好。
实际上,线程不是占用堆内存空间,java每次发起一个线程,jvm会向操作系统请求一个本地线程,此时操作系统会用
空闲内存来分配这个线程。所以java里线程并不会占用jvm的内存空间,
而是会占用操作系统空闲的内存空间
2、提高响应速度: 当任务到达时,任务可以不需要等到线程创建就能立即执行。
3、提高线程的可管理性
线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌
二.线程池中线程复用原理
在java中,每个Thread类都有一个start方法,在程序调用start方法启动线程时候,java会调用该类的run方法。在Thread类的run方法中其实调用了Runnable对象的run方法,因此可以继承Thread类,在start方法中不断循环调用传递进来的Runnable对象,程序就会不断执行run方法中的代码中的代码。可以将Runnable对象存放到Queue中,当线程在获取下一个Runnable对象之前可以阻塞,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行,这样就实现了线程池,达到复用的效果
三.线程池代码实现
public static void main(String[] args) {
System.out.println("主线程"+Thread.currentThread().getName());
** workQueue:工作队列,保存未执行的Runnable 任务**
LinkedBlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(10);
**threadFactory:当执行者创建一个新线程时要使用的工厂**
ThreadFactory threadFactory = new ThreadFactory() {
// int i = 0; 用并发安全的包装类
AtomicInteger atomicInteger = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
//创建线程 吧任务传进来
Thread thread = new Thread(r);
// 给线程起个名字
thread.setName("MyThread" + atomicInteger.getAndIncrement());
return thread;
}
};
**创建线程池**
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8, 1,
TimeUnit.SECONDS, blockingQueue, threadFactory,new ThreadPoolExecutor.DiscardOldestPolicy());
**执行多线程**
for (int i = 0; i < 20; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
try {
method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
** 线程中需要运行的方法**
private static void method() throws InterruptedException {
System.out.println("ThreadName" + Thread.currentThread().getName() + "进来了");
Thread.sleep(2000);
System.out.println("ThreadName" + Thread.currentThread().getName() + "出去了");
}
}
执行结果:
四.自定义线程池构造函数的参数
- corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true
- maximumPoolSize:线程池允许的最大线程池数量
- keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
- unit:超时时间的单位
- workQueue:工作队列,保存未执行的Runnable 任务
- threadFactory:当执行者创建一个新线程时要使用的工厂
- handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。
五.提交一个任务到线程池中,线程池的处理流程如下:
- 1、是否达到核心的线程数量?没有,通过线程池的线程工厂创建一个新的工作线程来执行任务
- 2、工作队列是否已经满了?没有,则将新的任务提交到工作队列里面
- 3、是否已经达到线程池的最大数量?如果没有,则创建一个新的工作线程来执行任务。
- 4、最后,如果还不够的话则执行拒绝策略来执行任务。
六.线程池的四种拒绝策略
- AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,线程池默认策略。
- DiscardPolicy:不执行新任务,也不抛出异常,基本上为静默模式。
- DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行。
- CallerRunPolicy:拒绝新任务进入,如果该线程池还没被关闭,那么这个新的任务在执行线程中被调用。
七、线程池的五种运行状态
- RUNNING :该状态的线程池既能接受新提交的任务,又能处理阻塞队列中任务。
- SHUTDOWN:该状态的线程池不能接收新提交的任务,但是能处理阻塞队列中的任务。 处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。
- STOP:该状态的线程池不接受新提交的任务,也不处理在阻塞队列中的任务,还会中断正在执行的任务。 在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用shutdownNow() 方法会使线程池进入到该状态
- TIDYING:如果所有的任务都已终止,workerCount (有效线程数)=0。 线程池进入该状态后会调用 terminated() 钩子方法进入TERMINATED 状态
- TERMINATED:在terminated()钩子方法执行完后进入该状态,默认terminated()钩子方法中什么也没有做。
八.线程池中常见的队列
- ArrayBlockingQueue:有界队列,是一个用数组实现的有界阻塞队列,按FIFO排序量。
- LinkedBlockingQueue:可设置容量队列,基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列。
- DelayQueue:延迟队列,是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
- PriorityBlockingQueue:优先级队列,是具有优先级的无界阻塞队列。
- SynchronousQueue:同步队列,一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
九.线程池工具类Executors【面试重点】
Java通过Executors(jdk1.5并发包)提供四种创建线程池的方法,分别为:
- newFixedThreadPool: 创建一个固定大小、任务队列容量无界的线程池。核心线程数等于最大线程数
- newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
- newCachedThreadPool:创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到线程池中,如果线程池有空闲的线程,则使用空闲的线程执行,如果没有则创建新线程执行。线程池的线程空闲超过60s,将会被销毁释放。线程池的核心线程数等于0,最大的线程数为Integer.MAX_VALUE
- newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
说明:Executors返回的线程池对象的弊端如下:
- newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
- newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE(2的31次方-1,int类型最大值),可能会创建数量非常多的线程,甚至OOM。
- 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
十.线程池源码分析
1.ThreadPoolExecutor中execute方法的源代码实现
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 线程池本身的状态跟worker数量使用同一个变量ctl来维护
int c = ctl.get();
// 通过位运算得出当然线程池中的worker数量与构造参数corePoolSize进行比较
if (workerCountOf(c) < corePoolSize) {
// 如果小于corePoolSize,则直接新增一个worker,并把当然用户提交的任务command作为参数,如果成功则返回。
if (addWorker(command, true))
return;
// 如果失败,则获取最新的线程池数据
c = ctl.get();
}
// 如果线程池仍在运行,则把任务放到阻塞队列中等待执行。
if (isRunning(c) && workQueue.offer(command)) {
// 这里的recheck思路是为了处理并发问题
int recheck = ctl.get();
// 当任务成功放入队列时,如果recheck发现线程池已经不再运行了则从队列中把任务删除
if (! isRunning(recheck) && remove(command))
//删除成功以后,会调用构造参数传入的拒绝策略。
reject(command);
// 如果worker的数量为0(此时队列中可能有任务没有执行),则新建一个worker(由于此时新建woker的目的是执行队列中堆积的任务,
// 因此入参没有执行任务,详细逻辑后面会详细分析addWorker方法)。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果前面的新增woker,放入队列都失败,则会继续新增worker,此时线程池的状态是woker数量达到corePoolSize,阻塞队列任务已满
// 只能基于maximumPoolSize参数新建woker
else if (!addWorker(command, false))
// 如果基于maximumPoolSize新建woker失败,此时是线程池中线程数已达到上限,队列已满,则调用构造参数中传入的拒绝策略
reject(command);
}
十一.ThreadPoolExecutor源码中的重要成员变量
1、HashSet workers
workers是包含线程池中所有工作线程worker的集合,仅仅当拥有mainLock锁时才能访问它;
2、BlockingQueue workQueue
workQueue是用于持有任务并将其转换成工作线程worker的队列
3、AtomicInteger ctl
-
AtomicInteger类型的ctl代表了ThreadPoolExecutor中的控制状态,它是一个复核类型的成员变量,是一个原子整数,一个int字节,共32位。其中前三位代表线程池运行状态(runState),低29位代表线程池中当前活动的线程数量(workerCount)。定义为:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); -
ThreadPoolExecutor中COUNT_BITS就代表了workerCount所占位数 ,定义为:
private static final int COUNT_BITS = Integer.SIZE - 3; -
RUNNING:接受新任务,并处理队列任务
private static final int RUNNING = -1 << COUNT_BITS; -1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低 29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912; -
private static final int SHUTDOWN = 0 << COUNT_BITS; //00000000 00000000 00000000 00000000
-
private static final int STOP = 1 << COUNT_BITS; //00100000 00000000 00000000 00000000
-
private static final int TIDYING = 2 << COUNT_BITS; //01000000 00000000 00000000 00000000
-
private static final int TERMINATED = 3 << COUNT_BITS; //01100000 00000000 00000000 00000000