文章目录
1 架构说明
- Java中线程池是通过Executor框架实现的,该框架中用到了Executor,
Executors
,ExecutorService,ThreadPoolExecutor
这几个类。 - 注意对比:数组:Array–>Arrays;集合类:Collection–>Collections; 线程池:Executor–>Executors。加个
s
表示工具类。
2 线程池的实现
- Executors.newFixedThreadPool(int n); 创建一个
定长
的线程池,使用的阻塞队列是LinkedBlockingQueue
。用来执行长期的任务,性能好很多。 - Executors.newSingleThreadExecutor();创建一个
单线程化
的线程池,使用的阻塞队列是LinkedBlockingQueue
。用于一个任务一个任务执行的场景。 - Executors.newCachedThreadPool();创建一个
可缓存
的线程池,使用的阻塞队列是SynchronousQueue
。用于执行很多短期异步的小程序或者负载较轻的服务器。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 一池5个处理线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 一池1个处理线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 一池n个处理线程
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t is running");
});
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
3 线程池优势
线程复用
,控制最大并发数
,管理线程
。
- 其他池化技术:线程池、数据库连接池、Http连接池等等,都是为了减少获取资源的消耗,提高对资源的利用效率
4 线程池的7个重要参数
- 1
corePoolSize
:线程池中的常驻核心线程数 - 2
maximumPoolSize
:线程池能够容纳同时执行的最大线程数,此值必须大于等于1 - 3
keepAliveTime
:多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止 - 4
unit
:keepAliveTime的单位。 - 5
workQueue
:任务队列,被提交但尚未被执行的任务。 - 6
threadFactory
:表示生成线程池中工作线程的线程工厂,用于创建线程。一般用默认
的即可。 - 7
handler
:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何拒绝请求执行的策略
AbortPolicy(默认):直接抛出RejectedExecutionException异常
阻止系统正常运行。
CallerRunsPolicy:”调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退
到调用者,从而降低新任务的流量。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上拒绝策略都实现了RejectedExecutionHandler接口。
线程池工作原理:先启用corePool中的线程,当任务还在增加时,安排至阻塞队列中等待。当阻塞队列满了后,且任务还在增加,会启动maximumPool。当maximumPool中的线程全部启用后:(1)任务还在增加,且阻塞队列已满,则根据拒绝策略,拒绝任务执行。(2)任务不再增加,根据存活时间关闭maximumPool中,非corePool的线程。
线程池的主要流程(三层判断)
线程池执行流程小总结:
5 示例
MyRunnable.java
import java.util.Date;
public class MyRunnable implements Runnable {
private String command;
public MyRunnable(String command) {
this.command = command;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start.Time = " + new Date());
// 这个线程执行5秒钟左右
processCommand();
System.out.println(Thread.currentThread().getName() + " End.Time = " + new Date());
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
ThreadPoolDemo.java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
// 核心线程数为5
private static final int CORE_POOL_SIZE = 5;
// 最大线程数为10
private static final int MAX_POOL_SIZE = 10;
// 任务队列容量为100
private static final int QUEUE_CAPACITY = 100;
// 等待时间为1L
private static final long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
// 等待时间的单位为秒
TimeUnit.SECONDS,
// 任务队列容量为100
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
// 饱和策略为CallerRunsPolicy
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 10; i++) {
// 创建线程对象
Runnable worker = new MyRunnable("" + i);
// 添加到线程池,执行线程
executor.execute(worker);
}
// 终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finish all threads");
}
}
说明
通过实现
Runnable
接口创建线程,通过ThreadPoolExecutor
创建线程池
线程池每次会同时执行5个任务,这5个任务执行完后,剩下的五个才会被执行
execute()方法和submit()方法区别
execute()用于提交不需要返回值
的任务,因此无法判断任务是否被线程池执行成功
submit()用于提交需要返回值
的任务。线程池会返回一个Future
类型的对象,可以判断任务是否执行成功。
6 合理配置线程池的参数(线程池最大线程数)
看业务类型
- CPU密集型:CPU核心数+1。
- IO密集型:第一种,如果IO密集型任务线程并不是一直在执行任务,则应该配置进可能多的线程,如CPU核心数*2。第二种,如果该任务需要大量的IO,即大量的阻塞,参考公式:
CPU核心数/(1-阻塞系数)
。阻塞系数在0.8~0.9之间。例如8核CPU:8/(1-0.9)=80个线程数。
参考资料
1.JavaGuide公众号文章
2.尚硅谷Java大厂面试题全集(java面试,周阳主讲)-Java面试_大厂高频面试题_阳哥