线程池
2021/5/23 18:30:22
本文主要是介绍线程池,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
线程池的创建方式
1.线程池的创建方式1:创建固定个数的线程池
(任务数趋向无限大、建议谨慎使用)
//创建固定个数的线程池 ExecutorService service = Executors.newFixedThreadPool(10); for (int i = 0; i < 2 ; i++) { //执行任务 service.execute(new Runnable() { @Override public void run() { System.out.println("线程名:" + Thread.currentThread().getName()); } }); }
经典易错题:创建了10个线程来执行2个任务,问当前程序创建了几个线程?
答:2个
线程池的执行流程:当拿到一个任务之后,会判断当前线程池里面的线程数量是否达到了最大值,如果没有达到创建新的线程执行任务。当任务来了之后,线程池的线程数量已经是最大值,并且没有空闲线程,当前的线程会被发放到任务队列里面等待执行
线程池包含的两个重要内容:
1:命名
2:任务队列
自定义线程池的行为:(设置线程池的命名,线程池的优先级)
//线程工厂 MyThreadFactory myThreadFactory = new MyThreadFactory(); //创建线程池 ExecutorService service = Executors.newFixedThreadPool(10,myThreadFactory);
2.线程池的创建方式2:创建带缓存的线程池
//创建带缓存的线程池 ExecutorService service = Executors.newCachedThreadPool();
适用场景:根据任务的数量生成对应的线程数,适用于大量的短期任务的时候
3.线程池的创建方式3:创建一个执行定时任务的线程池
方法1:service.scheduleWithFixedDelay
(以上一个任务的结束时间作为开始时间)
//执行任务 service.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println("执行任务:"+new Date()); } },1,3, TimeUnit.SECONDS);//延迟1s开始执行定时任务,每隔3s执行一次 }
service.scheduleWithFixedDelay的四个参数:
参数1:线程池的任务
参数2:定时任务延迟多长时间开始执行
参数3:定时任务执行频率
参数4:配合参数2和参数3使用的时间单位
方法2:service.schedule
service.schedule(new Runnable() { @Override public void run() { System.out.println("执行任务:"+new Date()); } },3, TimeUnit.SECONDS);//即时每隔3s执行一次
schedule区别:
1:没有延迟执行的时间设置
2:定时任务只能执行一次
方法3:service.scheduleAtFixedRate
//执行任务 service.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行任务:"+new Date()); } },1,3, TimeUnit.SECONDS); }
方法3:scheduleAtFixedRate:开始时间是以上次任务的开始时间做起始时间的(固定频率执行)
方法1:service.scheduleWithFixedDelay:任务开始时间是以上次任务执行的结束时间作为开始时间的(时间不固定,根据任务执行的时间来定)
4.线程池的创建方式4:创建单个执行定时任务的线程池
//创建单个执行定时任务的线程池 ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
5.线程池的创建方式5:创建单个线程的线程池
//创建单个线程的线程池 ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); //执行任务(一个线程执行多个任务) for (int i = 0; i < 10; i++) { service.execute(new Runnable() { @Override public void run() { System.out.println("线程名:"+ Thread.currentThread().getName()); } }); }
经典面试题:单个线程的线程池有什么意义?
答:1.无需频繁的创建和销毁线程
2.可以更好地分配和管理以及存储任务(将任务存放在任务队列)
6线程池的创建方式6(JDK8+):根据当前的工作环境(CPU核心数、任务量)生成对应的线程池 异步线程池
synchronized:同步
同步:按照某种规则按序执行就叫同步
异步:根据当前工作环境来创建线程
同步执行的流程:
1.main调用线程池
2.线程池执行完之后
3.关闭线程池,main也会随之关闭
异步执行的流程:
1.main调用异步线程池
2.异步线程池后台执行,对于main线程来说异步线程池已经执行完成,关闭main线程
7线程池的创建方式7:
前六种Executors创建线程池可能存在的的问题:
1.线程数量不可控( Integet.MAX_VALUE)(线程的过度切换和争取 -> 程序执行比较慢)
2.任务数量不可控(任务数无限大 Integet.MAX_VALUE),当前任务量比较大的情况下就会造成内存溢出异常(OOM)
//原始的创建线程池的方法 ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000)); for (int i = 0; i < 2 ; i++) { executor.execute(new Runnable() { @Override public void run() { System.out.println("线程名:" +Thread.currentThread().getName()); } }); }
线程池创建线程是懒加载,当执行任务的时候才创建线程,并不是new的时候
1、问:假设:核心线程数:5 最大线程数:5 任务队列:5
当任务 = 11:
第一步:11-5 = 6
第二步:6-5 = 1
第三步:最大线程数-线程池的线程数量 = 0;线程池执行拒绝策略
拒绝策略:
(1)自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(5), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { //自 定义拒绝策略 System.out.println("执行了自定义拒绝策略"); } });
(2)JDK四种策略:
- 默认的拒绝策略
- 使用线程池的线程来执行任务,使用主线程来执行任务
- new ThreadPoolExecutor.DiscardPolicy()
忽略新的任务
new ThreadPoolExecutor.DiscardOldestPolicy()
忽略老任务
线程池的两种执行方式:
1、execute执行(new Runnable)无返回值的
2、submit执行(new Runnable无返回值/new Callable有返回值)
//返回返回值 Future<Integer> future = executor.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int num = new Random().nextInt(10); System.out.println("线程池生成了随机数"+num); return num; } }); System.out.println("main得到返回值" + future.get()); }
-
区别:
1、execute只能执行Runnable任务,他是无返回值的;submit他既能执行Runnable无返回值的任务,也可以执行Callable有返回值的任务
2、execute知心人跟吴如果有OOM异常 会将异常打印到控制台;submit执行任务出现OOM异常 不会打印异常 -
线程池的特征:
线程池相比于线程来说是长生命特征的,即使没有任务也会运行并等待执行任务 -
线程池的关闭:
1、executor.shutdown() :拒绝新任务加入,等待线程池中的任务队列执行完后,再停止线程池
2、executor.shutdownNow(): 拒绝执行新任务,不会等待任务队列中的任务执行完成,就停止线程池 -
线程池状态:(只是给开发者可见的 ,客户机是不可见的)
注意:线程池状态不是线程状态(6种)
ThreadLocal:
1、set()将变量存放在线程中
2、get()从线程中取得私有变量
3、remove()从线程中移除私有变量
问:什么时候不会执行initialValue?为什么不会执行?
- set之后就不执行了 ThreadLocal是懒加载的,当调用了get()方法之后,
初始化方法2:withInitial
1、
static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() { @Override public String get() { System.out.println("执行了初始化方法"); return "Java"; } }); public static void main(String[] args) { String result = threadLocal.get(); System.out.println("结果是:" + result); }
2、
static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Java"); public static void main(String[] args) { String result = threadLocal.get(); System.out.println("结果是:" + result); }
ThreadLocal 使用场景:
1、解决线程安全问题
2、实现线程级别的数据传递
(以下示例)
public static void main(String[] args) { //模拟用户的登录 User user = new User(); user.setUsername("Java"); ThreadLocalUtil.threadLocal.set(user); //调用订单系统 Order order = new Order(); order.getOrder(); //调用日志系统 Logger logger = new Logger(); logger.addLog(); } static class ThreadLocalUtil{ static ThreadLocal<User> threadLocal = new ThreadLocal<>(); } static class User{ private String username; public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } } /* * 日志系统*/ static class Logger { public void addLog(){ //得到用户信息 User user = ThreadLocalUtil.threadLocal.get(); // System.out.println("添加日志:"+user.getUsername()); } } static class Order{ public void getOrder(){ User user = ThreadLocalUtil.threadLocal.get(); // System.out.println("订单列表:" + user.getUsername()); } }
ThreadLocal缺点:
1:不可继承(子线程不能访问父线程的值)
static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { //set ThreadLocal threadLocal.set("Java"); Thread t1 = new Thread(new Runnable() { @Override public void run() { //get ThreadLocal String result = threadLocal.get(); System.out.println("结果是:" + result); } }); t1.start();
使用ThreadLocal.InheritableThreadLocal可继承:但是同级线程不可继承
2:脏读(脏数据):在一个线程内读到了不属于自己的数据
- 线程使用ThreadLocal 不会出现脏读 -> 每个线程都是用自己的变量值和ThreadLocal
- 线程池里面使用 ThreadLocal 就会出现脏读,线程池会复用线程,复用线程之后,也会复用线程中的静态属性,从而导致了某些方法不会被执行,于是就出现了脏数据问题
脏数据的解决方案:
- 避免使用静态属性(静态属性在线程池中会复用)
- 使用remove
3:内存溢出问题:(最常出现的问题)、打开数据连接但未关闭
内存溢出:当一个线程执行完之后,不会释放这个线程所占用的内存,或者释放内存不及时的情况都叫做内存溢出。->线程不用了,但线程相关的内存还得不到相关的释放
这篇关于线程池的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-05小米13T Pro系统合集:性能与摄影的极致融合,值得你升级的系统ROM
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南