线程池
使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
ThreadPoolExecutor
1. 线程池状态
- ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
runState is stored in the high-order bits
状态 | value | 说明 |
---|---|---|
RUNNING(当线程池创建出来的初始状态) | 111 | 能接受任务,能执行阻塞任务 |
SHUTDOWN(调用shutdown方法) | 000 | 不接受新任务,能执行阻塞任务;能执行正在执行的任务 |
STOP(调用shutDownNow) | 001 | 不接受新任务,打断正在执行的任务,丢弃阻塞任务 |
TIDYING(中间状态) | 010 | 任务全部执行完,活动线程也没了 |
TERMINATED(终结状态) | 011 | 线程池终结 |
- 这里使用ctlOf方法可以同时携带2种状态的值(runState和workerCount),为什么要这么做?
- 如果要对runState和workerCount同时进行修改,需要进行2次cas,就不是原子操作了,需要加锁;现在只要一次cas操作即可修改2个变量的值,不需要加锁。
private static int ctlOf(int rs, int wc) { return rs | wc; }
2. ThreadPoolExecutor构造方法
1、corePoolSize--核心线程数
2、maximumPoolSize--最大线程数(应急线程数||空闲线程)
3、keepAliveTime--针对空闲线程的存活时间 如果超时了则把空闲的线程kill
4、unit--针对3的时间单位
5、workQueue--任务存放的队列
一般来说,我们应该尽量使用有界队列,如果任务数大于核心线程数,多出来的任务将在无界队列中等待,直到内存全部消耗完;
即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。
6、threadFactory--线程工厂,主要是产生线程,给线程起个自定义名字
7、handler--拒绝策略
3. 工作方式
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务;
- 当线程数达到核心线程数上限,这时再加入任务,新加的任务会被加入队列当中去;
- 前提是有界队列,任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程数目作为空闲线程来执行任务;
- 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。
- 空闲线程和核心线程在执行完之后,如果队列中有任务,这2种线程都会从队列中去获取任务,谁先抢到任务谁就执行。
4. 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ExecutorPoolTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
(r) -> new Thread(r, "t" + atomicInteger.incrementAndGet()),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
threadPoolExecutor.shutdown();
}
static class MyTask implements Runnable {
private final int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
log.info("{} 正在执行task{}", Thread.currentThread().getName(), taskNum);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("task{}执行完毕============", taskNum);
}
}
}
- 这里正常创建3个线程不会有问题,如果创建的线程数大于最大线程数+队列任务数,会报错
5. 超时演示
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 空闲线程超时的问题
**/
@Slf4j
public class ExecutorPoolTest2 {
static ThreadPoolExecutor threadPoolExecutor;
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
threadPoolExecutor = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
(r) -> new Thread(r, "t" + atomicInteger.incrementAndGet()),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
}
static class MyTask implements Runnable {
private final int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
log.debug("{} 正在执行task{}", Thread.currentThread().getName(), taskNum);
try {
Thread.currentThread().sleep(3100);
log.debug("线程数目{}", threadPoolExecutor.getPoolSize());
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("task{}执行完毕============", taskNum);
}
}
}
- 模拟空闲时间是2s
6. 线程数配置
1、任务数(6个)小于等于核心线程数(1个)+队列容量(5个)
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ExecutorPoolTest13 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(1, 100, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5));
for (int i = 0; i < 6; i++) {
//如果有6个线程,只会有一个线程在工作是核心线程,因为队列可以存的下多余的线程,所以不会启用空闲队列
//如果有7个线程,会启用空闲线程,线程池里的正在工作的线程数是2
threadPoolExecutor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启用的线程数量
log.info("size:{}", threadPoolExecutor.getPoolSize());
});
}
}
}
- 只有一个核心线程在工作
2、任务数(7个)大于核心线程数(1个)+队列容量(5个)
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ExecutorPoolTest13 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(1, 100, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5));
for (int i = 0; i < 7; i++) {
//如果有6个线程,只会有一个线程在工作是核心线程,因为队列可以存的下多余的线程,所以不会启用空闲队列
//如果有7个线程,会启用空闲线程,线程池里的正在工作的线程数是2
threadPoolExecutor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启用的线程数量
log.info("size:{}", threadPoolExecutor.getPoolSize());
});
}
}
}
- 一个核心线程和一个空闲线程在工作
3、核心线程数为0个
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ExecutorPoolTest12 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(0, 100, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000));
for (int i = 0; i < 6; i++) {
threadPoolExecutor.execute(()->{
log.info("size:{}",threadPoolExecutor.getPoolSize());
});
}
}
}
- 执行这个任务的是空闲线程,而且所有线程执行完3s(设置的过期时间)后,线程池会停止工作
核心线程与空闲线程的区别
- 空闲线程可以被销毁(有过期时间)
- 核心线程是不会被销毁的
- 线程池是如何保证核心线程不被销毁:核心线程拿到的第一个任务是直接分配的,调用take()(内部有await()方法)获取队列中的任务,如果队列为空,则一直阻塞当前线程;
- 空闲线程为什么会被销毁:空闲线程拿到的第一个任务是从队列中得到的,调用poll()方法。
1、如果核心线程数不为0,先启动核心线程执行任务,如果任务大于核心线程数,放入队列,等待核心线程释放后到队列中拿任务,如果队列满了,启动空闲线程,如果又超出空闲线程就拒绝 ;
2、如果核心线程数为0,先放入队列,然后启动空闲线程执行任务。
newFixedThreadPool
1. 静态方法
- 核心线程数 == 最大线程数(没有空闲线程被创建),因此也无需超时时间;
- 阻塞队列LinkedBlockingQueue可以指定容量(如果不指定,数量就是Integer.MAX_VALUE);
- 可以设置线程名称;
- 适用于任务量已知,相对耗时的任务。
2. 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ExecutorPoolTest3 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1,
(r) -> new Thread(r, "fisher"));
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
log.debug("newFixedThreadPool");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.info("start...");
executorService.shutdown();
}
}
newCachedThreadPool
1. 静态方法
- 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,全部都是空闲线程60s后回收;
- 一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源;
- 这种线程池比较灵活,对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
2. 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j
public class ExecutorPoolTest4 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
}
}
}
- 这里使用TimeUnit.SECONDS.sleep(1);然后每个线程顺序执行,而不是并发执行,使得newCachedThreadPool复用了pool-1-thread-1这个线程。
- 注释掉TimeUnit.SECONDS.sleep(1);再看打印结果,10个线程都是新创建的,没有发生复用,是因为10个任务同时放入SynchronousQueue队列,pool-1-thread-1只有处理完task1之后,才能处理task2,但是task2已经在队列当中,所以要重新创建一个线程处理其他任务。
newSingleThreadExecutor
1. 静态方法
- 多个任务排队执行,线程数固定为 1,任务数多于 1 时,会放入队列排队,即使任务执行完毕,这个唯一的线程也不会被释放。
- 区别于自己创建一个单线程串行执行任务(new Thread),如果任务执行失败而终止,那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。
- Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改;
- Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改,对外暴露的是 ThreadPoolExecutor对象,可以强转后调用 setCorePoolSize 等方法进行修改。
2. 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class ExecutorPoolTest5 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
executorService.execute(() -> {
log.info("{}", Thread.currentThread().getName());
int i = 1 / 0;
});
executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
}
}
- pool-1-thread-1发生异常后,创建pool-1-thread-2继续执行。
newScheduledThreadPool
1. 静态方法
- 创建一个线程池,可以设置线程池大小可以延时执行 、循环执行
2. 代码示例
- 延迟执行,schedule()
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ExecutorPoolTest9 {
static AtomicInteger i = new AtomicInteger();
static ScheduledExecutorService scheduledExecutorService;
public static void main(String[] args) {
log.info("main()");
scheduledExecutorService = Executors.newScheduledThreadPool(4);
//1.延迟2s执行
scheduledExecutorService.schedule(() -> {
log.info("延迟执行");
}, 2, TimeUnit.SECONDS);
//2.循环延迟3s执行
excuteTask();
}
public static void excuteTask() {
log.info("excuteTask,{}", i.incrementAndGet());
scheduledExecutorService.schedule(() -> {
excuteTask();
}, 3, TimeUnit.SECONDS);
}
}
- 循环执行,如果业务代码执行时间大于设置的时间间隔,则等待业务代码执行完,线程才再次执行
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ExecutorPoolTest10 {
public static void main(String[] args) {
log.info("main()");
AtomicInteger i = new AtomicInteger();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
//按固定时间间隔循环执行,延迟3s启动线程,并每隔1s执行一遍,period包含在线程执行时长之内
scheduledExecutorService.scheduleAtFixedRate(() -> {
log.info("固定时间间隔,循环执行{}", i.getAndIncrement());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 3, 1, TimeUnit.SECONDS);
}
}
- 固定延迟时间循环执行,业务代码2s,延迟1s,则每隔3s执行一次
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ExecutorPoolTest11 {
public static void main(String[] args) {
log.info("main()");
AtomicInteger i = new AtomicInteger();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
//按固定延迟时间循环执行,延迟3s启动线程,并每隔1s执行一遍,在线程执行完之后延迟delay时长再次执行
scheduledExecutorService.scheduleWithFixedDelay(() -> {
log.info("固定延迟执行{}", i.getAndIncrement());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 3, 1, TimeUnit.SECONDS);
}
}
提交任务
1. execute
- 提交一个任务,没有返回值。
2. submit
- 提交一个任务有返回值;
- 使用get()方法获取返回值,方法会阻塞,只有等待线程执行完,才能获取。
- 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
public class ExecutorPoolTest6 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> submit = executorService.submit(() -> {
log.info("{}", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "success!";
});
log.info("start");
log.info(submit.get());
log.info("end");
}
}
3. invokeAll
- 提交所有的任务
- 代码示例
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j
public class ExecutorPoolTest7 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future<String>> futures = executorService.invokeAll(Arrays.asList(
() -> {
log.info("1");
return "1";
},
() -> {
log.info("2");
return "2";
},
() -> {
log.info("3");
return "3";
},
() -> {
log.info("4");
return "4";
}
));
log.info("start");
futures.forEach(f -> {
try {
log.info(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
log.info("end");
}
}
4. invokeAny
- 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
package org.example.ThreadPool;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
@Slf4j
public class ExecutorPoolTest8 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
String str= executorService.invokeAny(Arrays.asList(
() -> {
TimeUnit.MILLISECONDS.sleep(3000);
log.info("1");
return "1";
},
() -> {
TimeUnit.MILLISECONDS.sleep(2000);
log.info("2");
return "2";
},
() -> {
TimeUnit.MILLISECONDS.sleep(1000);
log.info("3");
return "3";
},
() -> {
TimeUnit.MILLISECONDS.sleep(4000);
log.info("4");
return "4";
}
));
log.info("start");
log.info(str);
log.info("end");
}
}
停止任务
1. shutdown
线程池状态变为SHUTDOWN
不会接收新任务
但已提交任务会执行完
不会阻塞调用线程的执行
void shutdown();
2. shutdownNow
线程池状态变为 STOP
不会接收新任务
会将队列中的任务返回
并用 interrupt 的方式中断正在执行的任务
List< Runnable> 中保存了队列中排队的任务
List< Runnable> shutdownNow();
3. awaitTermination
调用 shutdown后,调用线程并不会等待所有任务运行结束,可以利用此方法等待
等待线程池成为终结状态执行,但是会提前(线程池内的线程执行时间小于timeout),或超时(线程池内的线程执行时间大于timeout),为了在线程池结束之后,做善后工作(比如释放资源)
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
合理地配置线程池
从以下几个角度来分析:
- 任务的性质:CPU 密集型任务、IO 密集型任务和混合型任务。
- 任务的优先级:高、中和低。
- 任务的执行时间:长、中和短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接。
CPU 密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程的线程池。 由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。(可以通过 Runtime.getRuntime().availableProcessors()方法获得当前设备的 CPU 个数)
优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可 以让优先级高的任务先执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先 级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果, 等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用 CPU。