常见线程池详解(一篇足以)
Posted 一位懒得写博客的小学生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见线程池详解(一篇足以)相关的知识,希望对你有一定的参考价值。
线程的缺点
- 线程的创建需要开辟内存资源:本地方法栈、虚拟机栈、程序计数器等线程私有变量的内存,频繁的创建和消耗会带来一定的性能开销;
- 使用线程不能很好的管理任务和有好的拒绝任务;
线程池
线程池
定义:使用池化技术来管理和使用线程的技术就叫做线程池。
-
线程池的优点
- 可以避免频繁的创建和消耗线程
- 可以更好的管理线程的个数和资源的个数
- 拥有更多的功能,线程池可以进行定时任务;
- 线程池可以更优化的拒绝不能处理的任务。
线程池的创建方式
- 创建固定的线程池(任务数取向无限大);
- 创建带缓存的线程(根据对应的数量生成对应的线程数,适用于短期大量任务);
- 创建可以执行定时任务的线程池;
- 创建单个执行时任务的线程池;
- 创建单个线程池;
- 根据当前的工作环境(CPU、任务量)生成对应的线程池。
第一种
创建固定个数的线程池
public static void main(String[] args) {
//创建固定个数的线程池
ExecutorService service = Executors.newFixedThreadPool(10);//创建10个线程
//执行任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
//执行结果
pool-1-thread-1
第二种
创建带缓存的线程池
//使用场景:短期有大量任务的时候使用
public static void main(String[] args) {
//创建带缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
//执行结果
pool-1-thread-1
自定义线程池规则
private static int count = 1;
//自定义线程工厂
static class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//自定义线程池的名称
thread.setName("myPool-" + count++);
//设置优先级
thread.setPriority(10);
return thread;
}
}
public static void main(String[] args) {
//创建线程工厂
ThreadFactory threadFactory = new MyThreadFactory();
ExecutorService executorService = Executors.newFixedThreadPool(10,threadFactory);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名" + Thread.currentThread().getName() + ",优先级:" + Thread.currentThread().getPriority());
}
});
}
}
//执行结果
线程名myPool-1,优先级:10
线程名myPool-5,优先级:10
线程名myPool-3,优先级:10
线程名myPool-4,优先级:10
线程名myPool-2,优先级:10
线程名myPool-6,优先级:10
线程名myPool-7,优先级:10
线程名myPool-8,优先级:10
线程名myPool-9,优先级:10
线程名myPool-10,优先级:10
第三种
可以执行定时任务的线程池
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
//方法一:每三秒执行一次
// service.scheduleAtFixedRate(new Runnable() {
// @Override
// public void run() {
// System.out.println("执行任务:" + new Date());
// }
// },1,3, TimeUnit.SECONDS);//1,线程执行的任务 2.延迟执行 3.定时任务执行的频率 4.配合2,3使用的,规定了时间的单位
//方法二:
// service.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("执行任务:" + new Date());
// }
// },1,TimeUnit.SECONDS);//只会执行一次
//方法三:每三秒执行一次
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:" + new Date());
}
},1,3,TimeUnit.SECONDS);
}
scheduleAtFixeRate:以上次任务开始的时间作为下一次任务的开始时间
scheduleWithFixedDelay:以上次任务的结束时间作为下一次任务的开始时间的
第四种
public static void main(String[] args) {
//创建单个执行定时任务的线程池
//是第三种的单机版本
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:" + new Date());
}
},1,2, TimeUnit.SECONDS);
}
//执行结果
执行任务:Thu May 20 22:14:56 CST 2021
执行任务:Thu May 20 22:14:58 CST 2021
执行任务:Thu May 20 22:15:00 CST 2021
执行任务:Thu May 20 22:15:02 CST 2021
执行任务:Thu May 20 22:15:04 CST 2021
执行任务:Thu May 20 22:15:06 CST 2021
执行任务:Thu May 20 22:15:08 CST 2021
执行任务:Thu May 20 22:15:10 CST 2021
执行任务:Thu May 20 22:15:12 CST 2021
执行任务:Thu May 20 22:15:14 CST 2021
执行任务:Thu May 20 22:15:16 CST 2021
。
。
。
第五种
创建单个线程
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
});
}
}
//执行结果
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
创建单个线程池有什么用?
- 可以避免频繁创建和消耗带来的性能开销;
- 有任务队列可以储存多余的任务;
- 有大量的任务不能处理的时候, 可以友好的执行拒绝策略;
- 可以更好的管理任务
第六种 (JDK8+)
根据当前的硬件CPU生成对应个数的线程池,并且是异步。
public static void main(String[] args) {
//创建一个异步(根据当前CPU生成的线程池)
ExecutorService service = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程池:" + Thread.currentThread().getName());
}
});
}
//等待线程池执行完成
while(!service.isTerminated()) {
}
}
//执行结果
线程池:ForkJoinPool-1-worker-1
线程池:ForkJoinPool-1-worker-3
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-5
线程池:ForkJoinPool-1-worker-3
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-1
线程池:ForkJoinPool-1-worker-4
线程池:ForkJoinPool-1-worker-5
-
同步执行的流程:
- main调用线程池
- 线程池执行完之后
- 关闭线程池,main也会随之关闭 异步线程执行的流程
- main调用异步线程池
- 异步线程池后台执行,对于main线程来说异步线程池已经执行完成,关闭main线程
线程池两个重要的对象:
- 线程
- 队列
Executors 创建线程池的问题
- 线程数量不可控(线程的过度切换和争取);
- 任务数量不可控(Integer.MAX_VALUE)可能导致内存溢出
第七种
static class MyOOMClass {
private byte[] bytes = new byte[1024 * 1024 * 1];
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
MyOOMClass oomClass = new MyOOMClass();
System.out.println("任务:" + finalI);
}
});
}
}
线程池创建线程是懒加载,有任务的时候才会创建线程,并不是new的时候就会创建线程。
拒绝策略:
- 默认的拒绝策略
new ThreadPoolExecutor.AbortPolicy()
- 使用调用线程池的线程来执行任务,使用主线程来执行任务;
new ThreadPoolExecutor.CallerRunsPolicy()
- 忽略新任务;
new ThreadPoolExecutor.DiscardPolicy()
- 忽略老任务;
new ThreadPoolExecutor.DiscardOldestPolicy()
- 自定义拒绝策略;
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(0), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
}
});
线程池的2种执行方式:
- execute 执行 (new Runnable)无返回值的 ;
- submit 执行 (new Runnable/new Callable 有返回值的);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(5));
Future<Object> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
//生成随机值
int num = new Random().nextInt(10);
System.out.println("生成随机数" + num);
//返回参数
return num;
}
});
System.out.println(future.get());
}
//执行结果
生成随机数6
6
执行方法的区别:
- Execute 只能执行 Runnable 任务,它是无返回值的;submit 它能执行 Runnable 又能执行Callable。
- execute 执行任务如果有OOM异常会将异常打印到控台;submit 执行任务出现了OOM 异常时不会打印异常。
线程池的特征:线程池相比较于线程来说是长生命周期,即使没有任务了,也会运行并等待任务。
线程池的关闭:
- shutdown:拒绝新任务加入,等待线程池中的任务队列执行完之后,再停止线程;
- shutdownNow:拒绝执行新任务,不会等待任务队列中的任务执行完,就停止线程池。
线程池状态
ThreadPoolExecutor 参数:
- 核心线程数;
- 最大线程数;
- 生存时间;
- 时间单位;
- 任务队列;
- 线程工厂;
- 拒绝策略;
ThreadLocal
线程级别的私有变量
-
使用
- Set(T) : 将变量存放到线程中;
- get():从线程中取得私有变量;
- remove() : 从线程中移除私有变量(脏读、内存溢出)。
- initialValue :初始化
- withInitial:初始化
面试题:什么情况下不会执行 initialValue? 为什么不执行?
set 之后就不会执行了,ThreadLocal 是懒加载的,当调用了get方法之后,才会尝试执行 intialValue(初始化)方法,尝试获取一下 ThreadLocal set 的值,如果获取到了值,那么初始化方法永远不会执行。
ThreadLocal 使用场景:
- 解决线程安全问题;
- 实现线程级别的数据传递;
ThreadLocal 缺点:
- 不可继承性:
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String result = threadLocal.get();
System.out.println(result);
}
});
t1.start();
}
//执行结果
null
//解决方案
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("Java");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String result = threadLocal.get();
System.out.println(result);
}
});
t1.start();
}
//执行结果
Java
- 脏数据:在一个线程中读取到了不属于自己的值
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 2; i++) {
MyTask t = new MyTask();
executor.execute(t);
}
}
static class MyTask extends Thread{
static boolean fir = true;
@Override
public void run() {
if(fir) {
threadLocal.set(this.getName());
fir = false;
}
String result = threadLocal.get();
System.out.println(this.getName() +
":" + result);
}
}
//执行结果
Thread-0:Thread-0
Thread-1:Thread-0
/*
解决办法:
1.避免使用静态属性(静态属性在线程池中会复用)
2.使用 remove 解决
*/
static class MyTa以上是关于常见线程池详解(一篇足以)的主要内容,如果未能解决你的问题,请参考以下文章