文章目录
1.线程
线程是调度 CPU 资源的最小单位,线程模型分为 KLT 模型与 ULT 模型,JVM 使用的 KLT 模型,Java 线程与 OS 线程保持 1:1 的映射关系,也就是说有一个 java 线程也会在操作系统里有一个对应的线程。
Java 线程有多种生命状态如下:
- NEW, 新建
- RUNNABLE, 运行
- BLOCKED, 阻塞
- WAITING, 等待
- TIMED_WAITING, 超时等待
- TERMINATED,终结
2.协程
协程(纤程,用户级线程),目的是为了追求最大力度的发挥硬件性能和提升软件的速度,协程基本原理是:在某个点挂起当前的任务,并且保存栈信息,去执行另一个任务;等完成或达到某个条件时,再还原原来的栈信息并继续执行(整个过程栈线程不需要上下文切换)。
Java 原生不支持协程,在纯Java代码需要使用协成的话需要引入第三方包,如:quasar。
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.7.6</version>
</dependency>
3.线程池
线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,因此 Java 中提供线程池对线程进行统一分配、调优和监控 。
3.1线程池的介绍
在Web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程进行处理。如果每次请求都新创建一个线程的话实现起来飞非常简单,但是存在一个问题:如果并发的请求非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此以来会大大降低系统的效率。可能出现服务器在每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
那么有没有一种办法执行完一个任务,并不被销毁,而是可以继续执行其他任务呢?
那就是线程池。线程池为了线程声明周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程。线程创建的开销被分摊到了多个任务上。
什么时候使用线程池?
- 单个任务处理时间比较短
- 需要处理的任务数量比较大
线程池的优势
- 重用存在的线程,减少线程创建,消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
3.2 线程的实现方式
Runnable,Thread ,Callable
//实现Runnable接口的类将呗Thread执行,表示一个基本的任务
public interface Runnable{
//run 方法就是它所有的内容,就是实际执行的任务
public abstract void run();
}
//Callable 同时是任务,与Runnable接口区别在于它接受泛型,同时它执行任务后带有返回内容
public interface Callable<V>{
//相对于run方法的带有返回值的call方法。
V call() throws Exception;
}
3.3 Executor 框架
Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。下图为它的继承和实现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DI0XYl7y-1666686901232)(E:\study\笔记\markdown\java\4.并发编程\img\Executor继承图.png)]
从图中可以看出Executor下有一个 重要子接口ExecutorService,其中定义了线程池的具体行为如下:
1)execute(Runnablecommand):履行Ruannable类型的任务
2)submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
3)shutdown():在完成已提交的任务后封闭办事,不再接管新任务
4)shutdownNow():停止所有正在履行的任务并封闭办事。
5)isTerminated():测试是否所有任务都履行完毕了。
6)isShutdown():测试是否该ExecutorService已被关闭。
3.4 线程池重点属性
private final AtomicInteger ctl=new AtomicInteger(ctlof(RUNNING,O));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY =(1<<COUNT_BITS) - 1;
ctl是对线程池的运行状态和线程池中有效线程的数量讲行控制的一个字段,它包含两部分的信息:线程池的运行状态(runState)和线程池内有效线程的数量(workerCoun),这单可以看到,使用了Inteaer类型来保存,高3位保存runState,低29位保存workerCountCOUNT BITS就是29CAPACITY就是1左移29位减1(29个
1),这个常量表示workerCount的上限值,大约是5亿
ctl相关方法
private static int runStateOf(int c) {returnc&~CAPACITY;}
private static int workerCountOf(int c) {return c &CAPACITY;}
private static int ctlOf(int rs, int wc) { return rs | wc; }
- runStateOf :获取运行状态
- workerCountOf:获取活动线程数;
- ctlOf:获取运行状态或活动线程数的值。
3.5线程池存在5中状态:
RUNNING <<COUNT BITS;//高3位为111
SHUTDOWN 日<<COUNT BITS; //高3位为000
STOP 1<<COUNT BITS;//高3位为001
TIDYING =2<<COUNTBITS;//高3位为010
TERMINATED=3<<COUNT BITS;//高3位为011
1、RUNNING
(1)状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理
(2)状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
2、SHUTDOWN
(1)状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2)状态切换:调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN
3、STOP
(1)状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2)状态切换:调用线程池的shutdownNow()接口时,线程池中IRUNNINGorSHUTDOWN)->STOP
4TIDYING
(1)状态说明:当所有的任务已终止,ct记录的“任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在 ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2)状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会田 SHUTDOWN>TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP_>TIDYING
5、TERMINATED
(1)状态说明:线程池彻底终止,就变成TERMINATED状态。
(2)状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由TIDYINGTERMINATED进入TERMINATED的条件如下:
- 线程池不是RUNNING状态:
- 线程池状态不是TIDYING状态或TERMINATED状态;
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
- workerCount 为0;
- 设置TIDYING状态成功
3.5线程池执行流程:
执行流程如上图:execute方法执行时1)先判断核心线程是否已经创建满(线程数是否等于核心线程数),若没满创建核心线程,若已经满了;2)当核心处理不出来的新任务将进入阻塞队列BlockingQueue,当队列满了;3)创建非核心线程,核心线程+非核心线程总数=线程池最大线程数;4)在处理不过来的任务将进入拒绝策略 RejecteExecutionHandler;常用的拒绝策略是 callerRunsPolicy :让提交任务的线程执行任务, DiscardOldestPolicy:老的任务丢弃新的任务放进去。
3.6线程池的具体实现
ThreadPoolExecutor 默认线程池;
ScheduledThreadPoolExecutor 定时线程池;
ThreadPoolExecutor 使用例子如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestExector {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,5000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(5));
for (int i=0;i<20;i++){
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("i = task :"+Thread.currentThread().getName());
}
},i);
}
threadPoolExecutor.shutdown();
}
}
ThreadPoolExecutor 构造方法参数解释:
-
corePoolSize:核心线程数,线程池中始终存活的线程数。
-
maximumPoolSize: 、最大线程数=非核心线程+核心线程数,线程池中允许的最大线程数。
-
keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
-
unit: 单位,参数 keepAliveTime 的时间单位,7 种可选。
TimeUnit.DAYS 天
TimeUnit.HOURS 小时
TimeUnit.MINUTES 分
TimeUnit.SECONDS 秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.NANOSECONDS 纳秒 -
workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7 种:
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列。
SynchronousQueue 一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列。
DelayQueue 一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue 一个由链表结构组成的无界阻塞队列。与 SynchronousQueue 类似,还含有非阻塞方法。
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列
较常用的是 LinkedBlockingQueue 和 Synchronous。线程池的排队策略与 BlockingQueue 有关 -
threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
-
handler:拒绝策略,拒绝处理任务时的策略,4 种可选,默认为 AbortPolicoy
AbortPolicy ` 拒绝并抛出异常。
CallerRunsPolicy 重试提交当前的任务,即再次调用运行该任务的 execute()方法。
DiscardOldestPolicy 抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy 抛弃当前任务。
3.6.1 线程数配置参考
cpu密集处理型:CPU核数+1;
IO操作 密集型(读取附件):2*CPU核数+1;
RocketMQ,Eureka,nacos:2*CPU
最佳线程数=CPU核数*[1+i/o耗时/CPU耗时]
4.0 未完待续