Python 官方文档:入门教程 => 点击学习
目录1. 你知道线程池吗?为什么需要使用线程池?小结:2. 你知道有多少种创建线程池的方式3. 线程池的五种状态你有了解吗?4. 你知道ThreadPoolExecutor的构造方法
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。
而在Java中, JVM 中每创建和销毁线程就需要资源和时间的损耗了,线程中也是存在上下文切换,这需要一定的开销,并且线程的创建并不是越多越好,而如果创建的线程数太多,上下文切换的频率就变高,可能使得多线程带来的好处抵不过线程切换带来的开销,就有点得不偿失了。
那我们需要如何管控好线程呢?
所以我们可以创建一个容器把线程数缓存在容器了,以便给他人使用,并且无需再自行创建和销毁线程。
线程池就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁。
使用线程池的好处:
jdk1.5以后提供一个Executors
工具类 ,里面提供一些静态工厂方法,生成一些常用的线程池。
newCachedThreadPool
:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
那我们来看看底层的方法和实现过程:
底层:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
我们实现的步骤:
public class ThreadPoolDemo {
public static void main(String[] args) {
threadPoolTest();
}
private static void threadPoolTest() {
// 1. 使用工厂类获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
// 2. 提交任务
for (int i = 1; i < 8; i++) {
executorService.submit(new MyRunnable(i));
}
}
}
// 我们的任务类
class MyRunnable implements Runnable {
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
// 打印是哪个线程的名称。
System.out.println(Thread.currentThread().getName() + "执行了任务" + id);
}
}
可以得到的结果:
可以发现,线程池的开启是一直运行的状态,而如果你想结束的话,可以使用一个shutdown方法即 executorService.shutdown();
每次任务都会创建多一个线程出来了。
我们可以看下newCacheThreadPool的运行流程如下:
当需要执行很多短时间的任务时,newCacheThreadPool
的线程复用率比较高, 会显著的提高性能。而且线程60s后会回收,意味着即使没有任务进来,newCacheThreadPool
并不会占用很多资源。
newFixedThreadPool
:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
还是一样看下底层和代码实现过程吧:
底层:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
代码实现过程:
public static void main(String[] args) {
// threadPoolTest();
threadPoolTest2();
}
private static void threadPoolTest2() {
// 1. 使用工厂类获取线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2. 提交任务
for (int i = 1; i < 8; i++) {
executorService.submit(new MyRunnable(i));
}
}
得到的结果:
pool-1-thread-2执行了任务2
pool-1-thread-1执行了任务1
pool-1-thread-3执行了任务3
pool-1-thread-3执行了任务6
pool-1-thread-1执行了任务5
pool-1-thread-2执行了任务4
pool-1-thread-3执行了任务7
我们可以发现,创建线程数量就是我们指定3,核心线程数量和总线程数量相等,都是传入的参数nThreads,所以只能创建核心线程,不能创建非核心线程。因为LinkedBlockingQueue
的默认大小是Integer.MAX_VALUE
,故如果核心线程空闲,则交给核心线程处理;如果核心线程不空闲,则入列等待,直到核心线程空闲。
与newCacheThreadPool的区别:
newSingleThreadExecutor
:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
还是一样看下底层和代码实现过程吧:
底层:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
代码实现过程:
public static void main(String[] args) {
// threadPoolTest();
// threadPoolTest2();
threadPoolTest3();
}
private static void threadPoolTest3() {
// 1. 使用工厂类获取线程池对象
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 2. 提交任务
for (int i = 1; i < 8; i++) {
executorService.submit(new MyRunnable(i));
}
}
得到的结果:
pool-1-thread-1执行了任务1
pool-1-thread-1执行了任务2
pool-1-thread-1执行了任务3
pool-1-thread-1执行了任务4
pool-1-thread-1执行了任务5
pool-1-thread-1执行了任务6
pool-1-thread-1执行了任务7
可以发现,只创建了一个线程,有且仅有一个核心线程( corePoolSize == maximumPoolSize=1
),使用了LinkedBlockingQueue
(容量很大),所以,不会创建非核心线程。所有任务按照先来先执行的顺序执行。如果这个唯一的线程不空闲,那么新来的任务会存储在任务队列里等待执行。
newScheduledThreadpool
:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
还是一样看下底层和代码实现过程吧:
底层:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
代码实现:
public static void main(String[] args) {
// threadPoolTest();
// threadPoolTest2();
// threadPoolTest3();
threadPoolTest4();
}
private static void threadPoolTest4() {
// 1. 使用工厂类获取线程池对象
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
// 2. 每个任务延迟两秒执行
for (int i = 1; i < 8; i++) {
scheduledExecutorService.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
}
System.out.println("看是不是我先执行了!");
}
可以看到的结果:
看是不是我先执行了!
pool-1-thread-1执行了任务1
pool-1-thread-1执行了任务4
pool-1-thread-2执行了任务2
pool-1-thread-3执行了任务3
pool-1-thread-2执行了任务6
pool-1-thread-1执行了任务5
pool-1-thread-3执行了任务7
我们可以发现,线程池只创建我们指定的线程数,并且返回的是一个继承了ExecutorService的ScheduledExecutorService
的接口。它给我们提供一些延迟的方法:
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
延迟时间单位是unit,时间数是delay,任务是Runnable类型的command。
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
而这个方法是上面方法的重载,不一样的是任务是Callable类型的
线程池它有以下五种状态:
具体有:
RUNNING
:这是最正常的状态,能正常接受新的任务,正常处理等待队列中的任务。SHUTDOWN
:不接受新的任务提交,但是会继续处理正在执行的业务并且也会处理阻塞队列中的任务。STOP
:不接受新的任务提交,不再处理等待队列中的任务,并且中断正在执行任务的线程。TIDYING
:所有的任务都执行完毕或销毁了,当前活动线程数为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()进入终止状态。TERMINATED
:线程池彻底终止,即terminated()方法结束后,线程池的状态就会变成这个。我们先来看看它的构造方法有哪些:
// 五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {...}
// 六个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {...}
// 六个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {...}
// 七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
我们再来详解下构造方法中涉及的7个参数,其中最重要5个参数就是第一个构造方法中的。
核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干,而非核心线程(临时工)如果长时间的闲置,就会被销毁。但是如果将
allowCoreThreadTimeOut
设置为true时,核心线程也是会被超时回收。
该值相当于核心线程数量 + 非核心线程数量。
非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true),
则会也作用于核心线程。
TimeUnit是一个枚举类型 ,包括以下属性:
NANOSECONDS : 1微毫秒
MICROSECONDS : 1微秒
MILLISECONDS : 1毫秒
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
当新任务来的时候,会先判断当前运行线程数量是否达到了核心线程数,如果达到了,就会被存放在阻塞队列中排队等待执行。
常用的几个阻塞队列:
1.ArrayBlockingQueue
2.SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
3.DelayQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。
4.LinkedBlockingQueue
链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE
,也可以指定大小。
还有两个非必须的参数:
创建线程的工厂 ,用于批量创建线程,统一在创建线程时设置一些参数,如是否守护线程、线程的优先级等。如果不指定,会新建一个默认的线程工厂。
拒绝处理策略,在线程数量大于最大线程数后就会采用拒绝处理策略,四种拒绝处理的策略为 :
ThreadPoolExecutor.AbortPolicy
:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException
异常。ThreadPoolExecutor.DiscardPolicy
:丢弃新来的任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。ThreadPoolExecutor.CallerRunsPolicy
:由调用线程处理该任务。昨天MyGirl跟我讲了一下她去银行办理业务的一个场景:
而实际上线程的流程原理跟这个一样,我们来看下处理任务的核心方法execute
,它的源码大概是什么样子的呢,当然我们也可以看源码中的注释,里面也写的很清楚。这里具体讲下思路。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 1. 获取ctl,ctl是记录着线程池状态和线程数。
int c = ctl.get();
// 2. 判断当前线程数小于corePoolSize核心线程,则调用addWorker创建核心线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 创建线程失败,需要重新获取clt的状态和线程数。
c = ctl.get();
}
// 3. 如果不小于corePoolSize,进入下面的方法。
// 判断线程池是否运行状态并且运行线程数大于corePoolSize,将任务添加到workQueue队列。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 3.1 再次检查线程池是否运行状态。
// 如果isRunning返回false(状态检查),则remove这个任务,然后执行拒绝策略。
if (! isRunning(recheck) && remove(command))
reject(command);
// 3.2 线程池处于running状态,但是没有线程,则创建线程加入到线程池中
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 4. 如果放入workQueue失败,则创建非核心线程执行任务,
// 如果这时创建非核心线程失败(当前线程总数不小于maximumPoolSize时),就会执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
我们可以大概看下思路图:
先解释下ctl
变量ctl定义为AtomicInteger,记录了“线程池中的任务数量”和“线程池的状态”两个信息。以高三位记录着线程池的状态和低29位记录线程池中的任务数量。
RUNNING : 111
SHUTDOWN : 000
STOP : 001
TIDYING : 010
TERMINATED : 011
最后总结一下执行过程:
mainLock.lock();。
这里还是直接用简单的ThreadPoolExecutor
创建吧,等后续写线程池相关文章,再详细写自己创建的线程池吧。
我们先创建一个任务类Task:
public class Task implements Runnable{
private int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "即将执行的任务是" + id + "任务");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完成的任务是" + id + "任务");
}
}
测试代码:
public class ThreadPoolExecutorDemo {
private static final int CORE_POOL_SIZE = 3;
private static final int MAX_POOL_SIZE = 5;
private static final int QUEUE_CAPACITY = 10;
private static final Long KEEP_ALIVE_TIME = 1l;
public static void main(String[] args) {
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
Task task = new Task( i);
//执行Runnable
executor.execute(task);
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("线程已经全部执行完");
}
}
得到的结果:
pool-1-thread-1即将执行的任务是0任务
pool-1-thread-3即将执行的任务是2任务
pool-1-thread-2即将执行的任务是1任务
pool-1-thread-1执行完成的任务是0任务
pool-1-thread-3执行完成的任务是2任务
pool-1-thread-1即将执行的任务是3任务
pool-1-thread-3即将执行的任务是4任务
pool-1-thread-2执行完成的任务是1任务
pool-1-thread-2即将执行的任务是5任务
pool-1-thread-3执行完成的任务是4任务
pool-1-thread-1执行完成的任务是3任务
pool-1-thread-3即将执行的任务是6任务
pool-1-thread-1即将执行的任务是7任务
pool-1-thread-2执行完成的任务是5任务
pool-1-thread-2即将执行的任务是8任务
pool-1-thread-3执行完成的任务是6任务
pool-1-thread-1执行完成的任务是7任务
pool-1-thread-3即将执行的任务是9任务
pool-1-thread-2执行完成的任务是8任务
pool-1-thread-3执行完成的任务是9任务
线程已经全部执行完
这篇文章就到这里了,如果这篇文章对你也有所帮助,希望您能多多关注编程网的更多内容!
--结束END--
本文标题: 每日六道java新手入门面试题,通往自由的道路--线程池
本文链接: https://www.lsjlt.com/news/129560.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0