聊聊线程模型及JAVA线程池的使用

389 阅读4分钟

线程

一、什么是线程

操作系统能够进行运算调度的最小单位。线程分为用户线程内核线程,内核线程与用户线程存在状态切换。用户态内核态上下文切换比较耗时。

二、线程状态

Untitled.png

二、线程模型

线程模型分为三种

  • 多对一线程模型(N:1)
  • 一对一线程模型(1:1)
  • 多对多线程模型(N:N)
  1. 多对一线程模型(N:1)

    多个用户线程映射到一个内核线程,用户线程建立在用户空间的线程库上,用户线程的建立、同步、销毁和调度完全在用户态中完成,对内核透明

Untitled.png

优点:

  • 线程的上下文切换都发生在用户空间,避免了模态切换(mode switch),减少了性能的开销。
  • 用户线程的创建不受内核资源的限制,可以支持更大规模的线程数量。

缺点

  • 所有的线程基于一个内核调度实体即内核线程,这意味着只有一个处理器可以被利用,浪费了其它处理器资源,不支持并行,在多处理器环境下这是不能够被接受的,如果线程因为 I/O 操作陷入了内核态,内核态线程阻塞等待 I/O 数据,则所有的线程都将会被阻塞。
  • 增加了复杂度,所有的线程操作都需要用户程序自己处理,而且在用户空间要想自己实现 “阻塞的时候把线程映射到其他处理器上” 异常困难
  1. 一对一线程模型(1:1) — JVM 使用的线程模型

    每个用户线程都映射到一个内核线程,每个线程都成为一个独立的调度单元,由内核调度器独立调度,一个线程的阻塞不会影响到其他线程,从而保障整个进程继续工作

Untitled.png

优点:

  • 每个线程自成独立调度单元,使用内核提供的线程调度功能及处理器映射,可以完成线程的切换,并将线程的任务映射到其他处理器上,充分利用多核处理器的优势,实现真正的并行

缺点:

  • 没创建一个用户级线程都需要创建一个内核线程雨其对应,因此需要消耗一定程度的内核资源,而内核资源是有限的,所以创建的线程数量也是有限的
  • 需要频繁进行用户态与内核态切换,开销比较大
  1. 多对多线程模型(N:N)

    内核线程和用户线程的数量比为 M : N,这种模型需要内核线程调度器和用户空间线程调度器相互操作,本质上是多个线程被映射到了多个内核线程。

20220309193505.jpg

优点:

  • 用户线程的创建、切换、析构及同步依然发生在用户空间,能创建数量更多的线程,支持更大规模的并发
  • 大部分的线程上下文切换都发生在用户空间,减少了模态切换带来的开销
  • 可以使用内核提供的线程调度功能及处理器映射,充分利用多核处理器的优势,实现真正的并行,并降低了整个进程被完全阻塞的风险。

三、线程池

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  1. corePoolSize-核心线程数(包括空闲线程),需要注意的是在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐时线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法
  2. maximumPoolSize-最大线程数
  3. keepAliveTime - (corePoolSize,maximumPoolSize) 空闲线程,超过改时间 回收这部分线程
  4. unit - keepAliveTime 单位(NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、DAYS)
  5. workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。将在下文中详细阐述
    1. ArrayBlockingQueue - FIFO 队列
    2. LinkedBlockingQueue - 无界队列
    3. PriorityBlockingQueue - 指定优先级队列
  6. threadFactory - 执行新建线程的工厂
  7. handler - 拒绝策略
    1. AbortPolicy - 中止策略
    2. DiscardPolicy - 抛弃策略
    3. DiscardOldestPolicy - 抛弃旧任务策略,先将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用PriorityBlockingQueue优先级队列,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用
    4. CallerRunsPolicy - 调用者运行

创建线程时机:

线程数< corePoolSize ,

submit task 时会创建一个线程

线程数 = corePoolSize,

队列满时 submit task 创建线程