线程池源码分析(总结的真的很好很实用)

为什么要用线程池?

简洁的答两点就行。

  1. 降低系统资源消耗。

  2. 提高线程可控性。

如何创建使用线程池?

JDK8提供了五种创建线程池的方法:

1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

2.(JDK8新增)会根据所需的并发数来动态创建和关闭线程。能够合理的使用CPU进行对任务进行并发操作,所以适合使用在很耗时的任务。

注意返回的是ForkJoinPool对象。

什么是ForkJoinPool:

使用一个无限队列来保存需要执行的任务,可以传入线程的数量;不传入,则默认使用当前计算机中可用的cpu数量;使用分治法来解决问题,使用fork()和join()来进行调用。

3.创建一个可缓存的线程池,可灵活回收空闲线程,若无可回收,则新建线程。

4.创建一个单线程的线程池。

5.创建一个定长线程池,支持定时及周期性任务执行。

上层源码结构分析

Executor结构:

Executor

一个运行新任务的简单接口

ExecutorService

扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法

AbstractExecutorService

对ExecutorService接口的抽象类实现。不是我们分析的重点。

ThreadPoolExecutor

Java线程池的核心实现。

ThreadPoolExecutor源码分析

属性解释

值的注意的是状态值越大线程越不活跃。

线程池状态的转换模型:

 

构造器

在向线程池提交任务时,会通过两个方法:execute和submit。

本文着重讲解execute方法。submit方法放在下次和Future、Callable一起分析。

execute方法:

总结一下它的工作流程:

  1. workerCount < corePoolSize,创建线程执行任务。

  2. workerCount >= corePoolSize&&阻塞队列workQueue未满,把新的任务放入阻塞队列。

  3. workQueue已满,并且workerCount >= corePoolSize,并且workerCount < maximumPoolSize,创建线程执行任务。

  4. 当workQueue已满,workerCount >= maximumPoolSize,采取拒绝策略,默认拒绝策略是直接抛异常。

通过上面的execute方法可以看到,最主要的逻辑还是在addWorker方法中实现的,那我们就看下这个方法:

addWorker方法

主要工作是在线程池中创建一个新的线程并执行

参数定义:

  • firstTask the task the new thread should run first (or null if none). (指定新增线程执行的第一个任务或者不执行任务)

  • core if true use corePoolSize as bound, else maximumPoolSize.(core如果为true则使用corePoolSize绑定,否则为maximumPoolSize。 (此处使用布尔指示符而不是值,以确保在检查其他状态后读取新值)。)

 

为什么需要持有mainLock?

因为workers是HashSet类型的,不能保证线程安全。

w = new Worker(firstTask);如何理解呢

Worker.java

可以看到它继承了AQS并发框架还实现了Runnable。证明它还是一个线程任务类。那我们调用t.start()事实上就是调用了该类重写的run方法。

Worker为什么不使用ReentrantLock来实现呢?

tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。

线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。

run方法又调用了runWorker方法:

总结一下runWorker方法的执行过程:

1、while循环中,不断地通过getTask()方法从workerQueue中获取任务

2、如果线程池正在停止,则中断线程。否则调用3.

3、调用task.run()执行任务;

4、如果task为null则跳出循环,执行processWorkerExit()方法,销毁线程workers.remove(w);

这个流程图非常经典:

除此之外,ThreadPoolExector还提供了tryAcquiretryReleaseshutdownshutdownNowtryTerminate、等涉及的一系列线程状态更改的方法有兴趣可以自己研究。大体思路是一样的,这里不做介绍。

Worker为什么不使用ReentrantLock来实现呢?

tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。

线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。

在runWorker方法中,为什么要在执行任务的时候对每个工作线程都加锁呢?

shutdown方法与getTask方法存在竞态条件.(这里不做深入,建议自己深入研究,对它比较熟悉的面试官一般会问)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值