六、线程与线程池

ThreadLocal

概念

ThreadLocal 是给每个线程配一个专属的变量副本,实现线程间的数据隔离,避免并发冲突

set方法,设置当前线程中 ThreadLocal 的变量值

get 方法,获取当前线程中 ThreadLocal 的变量值

remove 方法,从当前线程的 ThreadLocalMap 中删除与当前 ThreadLocal 实例关联的条目

ThreadLocalMap

ThreadLocalMap 是 ThreadLocal 类的静态内部类,它是一个定制的哈希表,专门用于保存每个线程中的线程局部变量

使用开放地址法来处理哈希冲突

原因:ThreadLocalMap 中的哈希值分散的比较均匀,很少会出现冲突。 ThreadLocalMap 经常需要清除无用的对象

线程池 ThreadPoolExecutor

概念

线程池可以管理一堆线程,让线程执行完任务之后不进行销毁,而是继续去处理其它已经提交的任务,实现线程的复用,避免线程的重复创建和销毁带来的性能开销

构造

corePoolSize

线程池中保持的最小线程数(这些线程处于空闲状态时是否会被回收,取决于配置allowCoreThreadTimeOut(true)

maximumPoolSize

线程池允许创建的最大线程数

keepAliveTime

超出corePoolSize后创建的线程存活时间或者所有线程最大存活时间,取决于配置allowCoreThreadTimeOut(true)

unit

keepAliveTime的时间单位

workQueue

任务阻塞队列,当线程数达到核心线程数后,会将任务存储在阻塞队列中

常见实现:ArrayBlockingQueue、LinkedBlockingQueue 等

threadFactory

线程池内部创建线程所用的工厂

定制能力:可以自定义线程名称、优先级、守护线程状态

handler

拒绝策略,当队列已满并且线程数量达到最大线程数量时,会调用该方法处理任务

内置策略:

AbortPolicy(默认):直接抛出RejectedExecutionException

CallerRunsPolicy:由提交任务的线程自己执行

DiscardPolicy:直接丢弃任务,不做任何处理

DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新提交

自己实现RejectedExecutionHandler接口

原理

运行过程

1)线程池刚刚构建时,只包含一个阻塞队列,没有任何线程(想要在执行之前创建好核心线程数,可以调用 prestartAllCoreThreads 方法来实现)

2)在“线程数”小于“核心线程数”之前,无论是否存在空闲线程,每来一个任务都会创建一个线程直接处理这个任务,处理任务完成后会尝试去阻塞队列获取任务

3)当“线程数”满足“核心线程数”后,新来的任务都会进入阻塞队列

4)当阻塞队列已满,又来了新任务,此时若“线程数”没有超过“最大线程数”,则创建一个非核心线程直接处理该任务(所以先提交的任务不一定先执行

5)若“线程数”达到“最大线程数”,会执行构建线程池时定义的拒绝策略

线程复用

1)使用addWork()把线程和任务一起封装为 Worker 对象,然后调用 runWorker 方法来让线程执行任务

2)runWorker 内部使用了 while 循环,当第一个任务执行完后,会不断地通过 getTask 方法获取任务,只要能获取到任务,就会继续执行任务。否则,就会调用 finally 块中将线程退出

1)Worker 继承了 AQS 具有锁的特性(独享锁、不可重入锁)

2)执行任务前调用 Worker 的 lock 方法,执行完任务后,调用 unlock 方法,这样可以通过调用 Woker 的 tryLock 方法来判断出当前线程是否正在执行任务

(调用 shutdown 方法关闭线程池时,就会使用这种方式尝试打断没有执行任务的线程)

获取任务与等待时间

核心线程:通过taskQueue.take()方法从队列中永久阻塞等待新任务。若队列为空,线程就会挂起,直到有新任务入队后被唤醒

非核心线程:通过taskQueue.poll(keepAliveTime, TimeUnit)方法限时等待。如果在设定的时间内没有获取到新任务,poll 方法将返回 null

线程池的关闭与五种状态

关闭线程池有shutdown方法与shutdownNow方法

RUNNING

(线程池创建后)

能够接收、处理新任务

SHUTDOWN

(RUNNING状态下,调用 shutdown 方法后)

线程池不再接收新任务,但会继续执行完阻塞队列中的任务和正在运行的线程

STOP

(RUNNING状态下,调用 shutdownNow 方法后)

既不接受新任务,也不处理阻塞队列中等待的任务(通常会返回队列中的任务列表)

对于正在运行的线程,会通过中断的方式进行尝试中断,目标是尽快终止所有工作

TIDYING

SHUTDOWN 状态下,任务队列为空且执行中任务为空时,变为 TIDYING 状态

STOP 状态下,线程池中执行中任务为空时,变为 TIDYING 状态

TERMINATED

(TIDYING 状态下调用terminated() 方法后)

线程池彻底终止

Executor 工具类自动构建线程池

能够通过静态方法快速构建线程池,但不推荐

例如:

newFixedThreadPool创建固定线程数量的线程池,由于使用LinkedBlockingQueue任务阻塞队列,队列的容量默认无限大,实际使用中会出现任务过多导致内存溢出

newCachedThreadPool创建核心数量无限大的线程池,当任务过多会创建大量线程,导致负载过高甚至服务宕机

固定线程数量的线程池

核心线程数=最大线程数

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

单线程的线程池

只有一个线程

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

无限大线程数量的线程池

maximumPoolSize设置为 int 最大值

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

定时调度的线程池

直接创建ScheduledThreadPoolExecutor对象

可以定时或以固定频率执行任务

public static ExecutorService createWorkStealingPool() {
    return Executors.newWorkStealingPool();
}

任务窃取的线程池

线程池中的每个线程维护一个双端队列(deque),线程可以从自己的队列中取任务执行。如果线程的任务队列为空,它可以从其他线程的队列中“窃取”任务来执行,达到负载均衡的效果

public static ExecutorService newWorkStealingPool() {
        // 核心实现:使用可用处理器数量作为并行级别创建ForkJoinPool
        return new ForkJoinPool(
            Runtime.getRuntime().availableProcessors(),  // 并行级别 = CPU核心数
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,  // 默认线程工厂
            null,  // 使用默认的异常处理器
            true   // 异步模式,支持工作窃取
        );
    }

自定义线程池

线程数

CPU 密集型:任务需要大量的计算,很少线程阻塞。线程数设置为 CPU 核心数+1

IO 密集型:任务需要大量的 io,存在大量阻塞,需要多线程加速。线程数设置为 2*CPU 核心数

Java 中获取 CPU 核心数的方法:Runtime.getRuntime().availableProcessors();

线程工厂

建议自定义线程工厂,并设置线程的名称,方便日志管理

有界队列

使用有上限的有界队列,防止内存溢出

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇