并发编程之线程和线程池

1. 线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

线程是调度CPU资源的最小单位,线程模型分为KLT模型与ULT模型,JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有 一个对应的线程。

线程和进程的主要区别:

进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

线程的生命状态:

  1. New:新建
  2. Runnable:运行
  3. Blockel:阻塞
  4. Waiting:等待
  5. Timed_Waiting:超时等待
  6. Terminated:终止

线程状态切换图:

在这里插入图片描述

2. 协程

协程(纤程,用户级线程),英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。

目的是为了追求最大力度的发挥硬件性能和提升软件的速度

基本原理是在某个点挂起当前的任务,并且保存栈信息,去执行另一个任 务;等完成或达到某个条件时,再还原原来的栈信息并继续执行(整个过程线程不需要上下文切换,所以不会像线程切换那样消耗资源)。

注意:

Java不支持协程,在纯java代码里需要使用协程的话需要引入第三方包,如:quasar

3. 线程池

3.1 简介

线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

3.2 应用场景
  1. 单个任务处理时间比较短
  2. 需要处理的任务数量很大
3.3 优点
  1. 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
  2. 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行
  3. 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换
  4. 提供更强大的功能,延时定时线程池
3.4 Executor框架

Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法

Executor下子接口ExecutorService,其中定义了线程池的具体行为:

  • execute(Runnable command):履行Ruannable类型的任务
  • submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future 对象
  • shutdown():在完成已提交的任务后封闭办事,不再接管新任务
  • shutdownNow():停止所有正在履行的任务并封闭办事
  • isTerminated():测试是否所有任务都履行完毕了
  • isShutdown():测试是否该ExecutorService已被关闭
3.5 线程池重点属性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

private static final int COUNT_BITS = Integer.SIZE ­ 3;

private static final int CAPACITY = (1 << COUNT_BITS) - 1;

// ctl相关方法
// 获取运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }

// 获取活动线程数
private static int workerCountOf(int c) { return c & CAPACITY; }

// 获取运行状态和活动线程数的值
private static int ctlOf(int rs, int wc) { return rs | wc; }

ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息:

  • 线程池的运行状态 (runState)
  • 线程池内有效线程的数量 (workerCount)
3.6 线程池的5种状态
RUNNING =1 << COUNT_BITS; //高3位为111

SHUTDOWN = 0 << COUNT_BITS; //高3位为000

STOP = 1 << COUNT_BITS; //高3位为001

TIDYING = 2 << COUNT_BITS; //高3位为010

TERMINATED = 3 << COUNT_BITS; //高3位为011

Running:

状态:线程池处在此状态时,能够接收新任务,以及对已添加的任务进行处理

状态切换:线程池的初始化状态是Running。换句话说,线程池被一旦被创建,就处于Running状态,并且线程池中的任务数为0

ShutDown:

状态:线程池处在此状态时,不接收新任务,但能处理已添加的任务

状态切换:调用线程池的shutdown()接口时,线程池由Running -> ShutDown

Stop:

状态:线程池处在此状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务

状态切换:调用线程池的shutdownNow()接口时,线程池由(Running or ShutDown ) -> Stop

Tidying:

状态:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为Tidying状态。当线程池变为Tidying状态时,会执行钩子函数terminated()。terminated()在 ThreadPoolExecutor类中是空的,若用户想在线程池变为Tidying时,进行相应的处理; 可以通过重载terminated()函数来实现

状态切换:调用线程池的shutdownNow()接口时,线程池由(Running or ShutDown ) -> Stop

Terminated:

状态:线程池彻底终止,就变成Terminated状态

状态切换:线程池处在Terminated状态时,执行完terminated()之后,就会由 Tidying - > Terminated

进入Terminated的条件:
					1.线程池不是Running状态
					2.线程池状态不是Tidying状态或Terminated状态
					3.如果线程池状态是ShutDown并且workerQueue为空
					4.workerCount为0
					5.设置Tidying状态成功

状态切换图:
在这里插入图片描述

3.7 线程池创建参数

线程池创建:

// 线程池创建
public ThreadPoolExecutor(int corePoolSize,
	int maximumPoolSize,
	long keepAliveTime,
	TimeUnit unit,
	BlockingQueue<Runnable> workQueue,
	ThreadFactory threadFactory,
	RejectedExecutionHandler handler)

// 提交任务
public void execute() //提交任务无返回值

public Future<?> submit() //任务执行完成后有返回值

1.corePoolSize:

线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

2.maximumPoolSize:

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

3.keepAliveTime:

线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime

4.unit:

keepAliveTime的单位

5.workQueue:

用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务
  • LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞 吐量通常要高于ArrayBlockingQuene
  • SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQuene
  • priorityBlockingQuene:具有优先级的无界阻塞队列

6.threadFactory:

ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。

7.handler:

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

  • AbortPolicy:直接抛出异常,默认策略
  • CallerRunsPolicy:用调用者所在的线程来执行任务
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
  • DiscardPolicy:直接丢弃任务
3.8 线程池监控相关API
public long getTaskCount() //线程池已执行与未执行的任务总数

public long getCompletedTaskCount() //已完成的任务数

public int getPoolSize() //线程池当前的线程数

public int getActiveCount() //线程池中正在执行任务的线程数量
3.9 线程池创建方式
一. 通过Executors工厂方法创建(不推荐)
 // 创建使用单个线程的线程池
ExecutorService es1 = Executors.newSingleThreadExecutor();

// 创建使用固定线程数的线程池
ExecutorService es2 = Executors.newFixedThreadPool(10);

// 创建一个会根据任务创建新线程的线程池
ExecutorService es3 = Executors.newCachedThreadPool();

// 创建拥有固定线程数量的定时线程任务的线程池
ScheduledExecutorService es4 = Executors.newScheduledThreadPool(10);

// 创建只有一个线程的定时线程任务的线程池
ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor();

注意:此种创建方法有OOM的风险

原因截图自阿里巴巴开发手册
在这里插入图片描述

二. 自定义创建
new ThreadPoolExecutor( int corePoolSize, 
						int maximumPoolSize, 
						long keepAliveTime,
						TimeUnit unit,
						BlockingQueue<Runnable> workQueue)

4. 线程池工作流程图

在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值