线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处.

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须对其实现原理了如指掌。

线程池的创建方式

总共有7种,总体来说分为2类:

1. 通过 ThreadPoolExecutor 创建的线程池;

2. 通过 Executors 创建的线程池。

线程池创建方式

  • Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  • Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
  • Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
  • Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
  • Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
  • Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
  • ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

创建固定大小线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);

// 无返回值
Future<?> submit = service.submit(new Runnable() {
@Override
public void run() {
System.out.println("Runnable创建方式" + Thread.currentThread().getName());
}
});

service.execute(new Thread(() -> {
System.out.println("Thread创建方式" + Thread.currentThread().getName());
}));

// 有返回值
Future<Integer> submit1 = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Callable创建方式" + Thread.currentThread().getName());
int i = new Random().nextInt();
return i;
}
});

System.out.println("返回结果:" + submit1.get());

}

创建可缓存的线程池,感觉线程任务数量创建

  1. 优点:在一定时间内可以重复使用这些线程,产生相应的线程池。
  2. 缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。
1
2
3
4
5
6
7
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int finailI = i;
service.submit(() -> {
System.out.println("i:" + finailI + "| 线程名称: " + Thread.currentThread().getName());
});
}

创建延时定时任务线程池

scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执⾏)。*

scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。*

schedule():可以设置延时时间,定时执行;

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
System.out.println("执行任务:" + LocalDateTime.now());

ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
pool.schedule(() -> {
System.out.println("执行子任务:" + LocalDateTime.now());

}, 3, TimeUnit.SECONDS);
}

创建单线程线程池

1
2
3
4
5
6
7
8
9
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务时间:" + LocalDateTime.now());
// 执行普通任务
for (int i = 0; i < 10; i++) {
int finalI = i;
service.submit(() -> {
System.out.println("线程名: " + Thread.currentThread().getName() + " " + finalI);
});
}

创建可抢占式线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
ExecutorService service = Executors.newWorkStealingPool();

for (int i = 0; i < 10; i++) {
int finalI = i;
service.submit(() -> {
System.out.println("线程名:" + Thread.currentThread().getName() + " " + finalI);
});


}

while (!service.isTerminated()) {
}

使用ThreadPoolExecutor创建

ThreadPoolExecutor参数说明

拒绝策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void main(String[] args) {
// 线程工厂
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("线程:" + thread.getName());
return thread;
}
};


ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程
5, // 最大线程
100, // 放弃时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(1), // 使用队列,队列数
factory, // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

for (int i = 0; i < 10; i++) {
int finalI = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务:" + finalI);
});
}

executor.shutdown();

}

执行流程

  1. 线程总数量 < corePoolSize,无论线程是否空闲,都会新建一个核心线程执行任务(让核心线程数量快速达到corePoolSize,在核心线程数量 < corePoolSize时)。注意,这一步需要获得全局锁。
  2. 线程总数量 >= corePoolSize时,新来的线程任务会进入任务队列中等待,然后空闲的核心线程会依次去缓存队列中取任务来执行(体现了线程复用)。
  3. 当缓存队列满了,说明这个时候任务已经多到爆棚,需要一些“临时工”来执行这些任务了。于是会创建非核心线程去执行这个任务。注意,这一步需要获得全局锁。
  4. 缓存队列满了, 且总线程数达到了maximumPoolSize,则会采取上面提到的拒绝策略进行处理。

线程池
https://zhaops-hub.github.io/2024/06/09/java/线程池/
作者
赵培胜
发布于
2024年6月9日
许可协议