前言
什么是线程池
存放若干个用于执行任务的线程的容器。
为什么要用线程池
性能
线程的创建和销毁是非常消耗系统资源的,每当我们要执行异步任务时,都创建一个新的线程,会带来大量额外的开销,并且线程的数量不宜过多,线程过多会增加CPU调度的压力,导致多线程程序反而性能下降。
线程池可以预先创建好若干个线程实例,随时准备执行任务。
安全
线程是非常珍贵的资源,CPU调度线程的能力是有限的,意味着线程的创建是有限的,一旦代码编写错误,不断的创建新线程,可能会导致系统崩溃。
统一管理
使用线程池可以方便的对线程进行统一管理,例如:停止接收任务并销毁所有线程。
内置线程池
为了方便开发人员使用,JDK提供了一些预定义的线程池,可以直接使用,如果不满足需求可以自定义一个线程池。
使用Executors类来快速的创建线程池。
newSingleThreadExecutor
创建一个单线程的线程池,任务会按顺序执行,任务提交时会阻塞,必须等待前一个任务执行完。
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
executorService.execute(()-> System.out.println(Thread.currentThread().getName()));
}
/**
* 输出:
* pool-1-thread-1
* pool-1-thread-1
* pool-1-thread-1
*/
}
newFixedThreadPool
创建一个固定线程数的线程池,来不及执行的任务会被放到任务队列中。
Executors.newFixedThreadPool(2);
newCachedThreadPool
创建一个带缓存的线程池,线程的数量范围在0~Integer.MAX_VALUE,默认线程空闲60秒就会被回收。
使用时需要注意,一旦线程数量过多可能导致系统崩溃。
Executors.newCachedThreadPool();
newScheduledThreadPool
创建一个固定核心线程数的线程池,支持任务定时执行。
-
scheduleAtFixedRate
上一个任务运行结束到下一个任务开始运行的时间间隔。 -
scheduleWithFixedDelay
上一个任务开始运行到下一个任务开始运行的时间间隔。
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
//延迟1s执行,每隔1秒输出当前时间
executorService.scheduleWithFixedDelay(() -> {
System.out.println(System.currentTimeMillis());
}, 1,1, TimeUnit.SECONDS);
}
newSingleThreadScheduledExecutor
创建一个单线程的线程池,支持任务定时执行。
Executors.newSingleThreadScheduledExecutor();
newWorkStealingPool
创建一个“工作密取”型的线程池,基于ForkJoinPool实现,适用于处理很耗时的操作。
ForkJoinPool和其他线程池相比较为特殊,可以多了解一下。
自定义线程池
ThreadPoolExecutor
使用ThreadPoolExecutor可以构建一个自己的线程池。
构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize
核心线程的大小,默认不会创建线程,当有任务需要执行时才会创建线程。
线程达到核心线程数时,不会再创建线程,会将任务放到队列中。 -
maximumPoolSize
达到核心线程数,且任务队列也放满时,会继续创建线程,直到线程数达到maximumPoolSize。 -
keepAliveTime
设置线程空闲多少时间会被回收,当线程数超过corePoolSize才会生效。 -
unit
keepAliveTime的时间单位。 -
workQueue
任务来不及处理时,存放的任务队列。 -
threadFactory
创建线程的工厂。 -
handler
饱和策略,当线程数达到maximumPoolSize仍然来不及处理任务时采取的措施:丢弃、抛异常、记录日志等…
例子
public class MyThreadPool {
//自定义饱和策略
private static class MyRejectedExecutionHandler implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("任务丢弃,记录日志...");
}
}
public static void main(String[] args) {
//任务队列 容量为2
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);
/**
* 构建自定义线程池
* 核心线程数:1
* 最大线程数:2
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60,
TimeUnit.SECONDS, queue, new MyRejectedExecutionHandler());
//并发时,最多接收4个任务,其他丢弃
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
SleepUtil.sleep(1000);
System.out.println(Thread.currentThread().getName());
});
}
/**
* 输出:
* 任务丢弃,记录日志...
* pool-1-thread-2
* pool-1-thread-1
* pool-1-thread-2
* pool-1-thread-1
*/
}
}
execute和submit
execute仅代表异步执行一个任务,没有返回值。
submit除了可以提交Runnable外,还可以提交Callable,Callble是支持返回结果的。
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<Integer> future = executorService.submit(() -> {
int sum = 0;
for (int i = 0; i < 100000; i++) {
sum += i;
}
return sum;
});
Integer result = future.get();
System.out.println(result);//704982704
}
手写线程池
有了多线程的学习基础,可以自己尝试实现一个线程池。
MyThreadPool
/**
* @Author: 潘
* @Date: 2019/11/26 17:02
* @Description: 自己实现线程池
*/
public class MyThreadPool {
private int coreSize;//核心线程数
private int maxSize;//最大线程池
private final int MAX_WAIT_QUEUE_SIZE = 10;//等待队列最大容量
private final LinkedBlockingQueue<Runnable> WAIT_QUEUE = new LinkedBlockingQueue<>(MAX_WAIT_QUEUE_SIZE);
//内置锁
private ReentrantLock lock = new ReentrantLock();
private BlockingQueue<MyThread> freeQueue;//空闲队列
private BlockingQueue<MyThread> workQueue;//工作队列
//关闭标记
private AtomicBoolean isShutdown = new AtomicBoolean(false);
public MyThreadPool(int coreSize, int maxSize) throws InterruptedException {
this.coreSize = coreSize;
this.maxSize = maxSize;
//初始化线程
freeQueue = new LinkedBlockingQueue<>();
for (int i = 0; i < coreSize; i++) {
freeQueue.put(new MyThread());
}
workQueue = new LinkedBlockingQueue<>();
}
public void execute(Runnable run){
if (isShutdown.get()) {
return;
}
lock.lock();
if (!freeQueue.isEmpty()) {
//有空闲线程
MyThread poll = freeQueue.poll();
poll.setRunnable(run);
workQueue.add(poll);
lock.unlock();
return;
}
//没有空余线程,放入等待队列
if (WAIT_QUEUE.size() < MAX_WAIT_QUEUE_SIZE) {
WAIT_QUEUE.add(run);
lock.unlock();
return;
}
if (freeQueue.size() + workQueue.size() < maxSize) {
//没有空余线程,等待队列也满了,但是还没到最大线程数
MyThread myThread = new MyThread();
workQueue.add(myThread);
myThread.setRunnable(run);
lock.unlock();
return;
}
lock.unlock();
//极限,丢弃任务
System.err.println("log:任务丢弃...");
}
//关闭
public void shutdown() {
isShutdown.set(true);
}
private class MyThread extends Thread{
private Runnable runnable;
void setRunnable(Runnable runnable){
this.runnable = runnable;
if (!isAlive()) {
start();
}else {
LockSupport.unpark(this);
}
}
@Override
public void run() {
while (true) {
try {
runnable.run();
}catch (Exception e){
e.printStackTrace();
}finally {
//当前任务执行完毕 等待队列是否有任务?
while (!WAIT_QUEUE.isEmpty()) {
WAIT_QUEUE.poll().run();
}
workQueue.remove(this);
freeQueue.add(this);
LockSupport.park();
}
}
}
}
}
//测试
class Client{
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(1, 2);
for (int i = 0; i < 20; i++) {
new Thread(() -> {
myThreadPool.execute(()->{
SleepUtil.sleep(100);
System.out.println(Thread.currentThread().getName());
});
}).start();
}
}
}
尾巴
构建线程池时,线程数不宜过多,一般来说:
- 对于计算密集型应用,线程数约等于CPU核心数,不宜过多。
- 对于IO密集型应用,线程数约为CPU核心数的2倍。
具体的数字需要结合压力测试给出的结果来决定。