[07]从零开始的JAVAEE

您所在的位置:网站首页 java线程池四种拒绝策略 [07]从零开始的JAVAEE

[07]从零开始的JAVAEE

2023-06-12 19:40| 来源: 网络整理| 查看: 265

 目录

一、什么是线程池

为什么从线程池拿线程比创建线程更为高效?

内核态

用户态

二、线程池的使用

线程池的构造方法

代码模拟实现线程池

分析

实现

一、什么是线程池

线程的创建,虽然相比于进程更为轻量,但在频繁创建的情况下,开销也是不可忽略的,为了提高效率,可以引入线程池,提前将线程创建好,放入池子中,后续需要用到线程时,直接从线程池中申请,用完以后还回去即可,这样就免去了一直创建销毁线程,提高了效率

为什么从线程池拿线程比创建线程更为高效?

为了理解这个问题,我们需要理解操作系统中的两个基本概念:用户态和内核态

一个操作系统 = 内核 + 配套的应用程序

内核:操作系统最核心的功能模块集合,例如硬件管理、各种驱动、进程管理......且内核需要给上层应用程序提供支持,例如打印一个hello,应用程序就需要调用内核,然后内核在通过操作系统调用显示器完成打印

应用程序在同一时刻会有许多,但内核只有一个,内核同时给多个应用程序提供服务,有时就会导致服务不那么及时

以一个银行取钱的例子做演示

 在这个例子中,柜台的服务员就相当于一个内核,来取钱的人就相当于应用程序

内核态

内核态就是服务员的操作,它的时间不可控,因为服务员也有可能在给你取钱的过程中上个厕所什么的

用户态

时间可控,用户可以自行决定时间

系统创建线程,就是一个内核态的操作,系统从线程池中拿线程,就是用户态的操作,这就是为什么线程池比创建线程更高效的原因

二、线程池的使用

标准库中提供了线程的线程池

ExecutorService pool = Executors.newFixedThreadPool(10);//创建一个有10个线程的线程池 //添加任务到线程池中 pool.submit(new Runnable(){ @Override public void run(){ System.out.println("hello"); } });

这里需要注意的是,此处线程池的创建并非直接new的对象,而是通过Executors类里面的静态方法完成的对象构造,这是典型的工厂模式,工厂模式会在后面详细介绍。

 Executors.newFixedThreadPool(10)这个类实际上是对ThreadPoolExecutor这个类进行进一步封装实现的,ThreadPoolExecutor是原装的线程池

线程池的构造方法

线程池的构造方法有很多,查阅官方文档可以查到这些

这里主要介绍最后一条构造方法

ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler );  int corePoolSize:核心线程数int maximumPoolSize:最大线程数

如果把线程池比作公司,核心线程数就是其中的正式员工,最大线程数就是正式员工+实习生。我们知道,正式员工需要与公司签订劳动合同,不能随便辞退,而实习生不签订,可以随便辞退,万恶的资本家就是这样剥削我们的,当公司里比较忙时,正式员工+实习生一起上,当比较清闲时,就把实习生给辞了

long keepAliveTime:存活时间TimeUnit unit:存活时间单位

当公司内较为清闲时,公司也无法预知在接下来的时间里会不会突然忙起来,所以不会立马把实习生都辞退了,它们会先让实习生待一段时间,如果一段时间过后,公司仍然清闲,说明接下来的一段时间内大概率是不会忙了,这段时间过后再把实习生给裁掉,这里等待的一段时间就是存活时间,存活时间单位是毫秒、秒、分钟这些单位

BlockingQueue workQueue:阻塞队列

线程池中要管理很多任务,所以我们可以手动给线程池添加一个阻塞队列,来方便控制、获取队列中的信息,submit方法就是把任务放到队列中

ThreadFactory threadFactory:线程工厂

工厂模式,就是创建线程的辅助类,无需做过多了解,知道即可

RejectedExecutionHandler handler:拒绝策略

当线程池中池子满了,再往其中添加任务,应该如何拒绝,下图是标准库中的四种拒绝策略 

如果满了,继续添加任务,直接抛出异常:例如你今天已经安排满了,你的上司还给你安排了任务,此时你绷不住了,哇的一声哭了出来谁添加,谁负责:你的上司给你安排任务之后,你回怼:我才不去,你自己安排的你自己去吧丢弃最老的任务:丢弃最后执行的安排,把上司安排的任务添加进去(阻塞队列的队首元素)丢弃最新任务:丢弃最先执行的安排,就是不管上司给你安排了什么,统统丢弃,还是该干嘛干嘛。 JAVA标准库创建线程池的四种方式

在java标准库中自带了四种创建线程池的方法

newFixedThreadPool(int n):创建一个固定大小的线程池,线程数量为 n 个,所有任务都会放入队列中等待执行。

ExecutorService pool = Executors.newFixedThreadPool(10);//创建十个线程的线程池

newCachedThreadPool():创建一个可缓存的线程池,线程数量不定,根据需要动态创建线程,线程空闲超过 60 秒将被终止并回收。

ExecutorService pool = Executors.newCachedThreadPool();//创建缓存线程池,线程数量不固定,动态开辟

newSingleThreadExecutor():创建一个单一线程池,线程数量为 1。所有任务都会交给该线程依次执行。

ExecutorService pool = Executors.newSingleThreadExecutor();//创建一个只有一个线程的线程池

newScheduledThreadPool(int n):创建一个定长线程池,线程数量为 n 个,可以按照指定的延迟时间或者固定间隔周期执行任务,功能强大。

ExecutorService executor = Executors.newScheduledThreadPool(2);//定长线程池,可以按照指定延迟时间执行任务 代码模拟实现线程池 分析

一个简易完整的线程池,具有以下的属性

阻塞队列:用于存放任务submit方法,用于添加任务创建固定数量的线程池:用于创建线程

线程池中线程的数量并不是越多越好,在实际开发中,需要参考cpu的核心数量,假设cpu核心数是N,一般设置成N、N+1、2N、1.5N。等等,需要实际测试才能得出具体结论

实现 class MyThreadPool { private BlockingDeque queue = new LinkedBlockingDeque();//阻塞队列,存放任务 /** * 用于添加任务到阻塞队列的方法 * @param task:任务 * @throws InterruptedException */ public void submit(Runnable task) throws InterruptedException { queue.put(task); } /** * 构造方法,参数为int,表示创建多少个工作线程 * @param n:线程的数量 */ public MyThreadPool(int n){ for (int i = 0; i < n; i++) { Thread t = new Thread(()->{ try { //一直取任务 使用while循环,当队列中没有任务时不会创建新的线程,所以线程不是恒定不变的,n只是一个上限 while (true){ Runnable runnable = queue.take();//取出任务 runnable.run();//运行 } } catch (InterruptedException e) { e.printStackTrace(); } }); t.start();//创建完就运行 } } }



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3