线程池简介

5. 线程池

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,

如果线程数量超过了最大数量,超出数量的线程排队等候,等其它线程执行完毕, 再从队列中取出任务来执行。

他的主要特点为线程复用;控制最大并发数;管理线程

5.1 为什么要用线程池?

线程池提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁的额外开销,提高了响应

的速度。线程池还维护一些基本统计信息,例如已完成任务的数量。使用线程池有很多好处:

  1. 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  2. 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。

  3. 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定

    性,使用线程池可以进行统一的分配,调优和监控。

5.2 线程池体系结构

5.2.1 线程池的组成

一般的线程池主要分为以下 4 个组成部分:

  1. 线程池管理器:用于创建并管理线程池

  2. 工作线程:线程池中的线程

  3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行

  4. 任务队列:用于存放待处理的任务,提供一种缓冲机制

Java 中的线程池是通过 Executor 框架实现的,该框架用到了 Executor,Executors,ExecutorService,

ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类。

image-20210803171353137

  • 线程池的体系架构

* java.util.concurrent.Executor : 负责线程的使用与调度的根接口

* |–ExecutorService 子接口: 线程池的主要接口

* |–ThreadPoolExecutor 线程池的实现类

* |–ScheduledExecutorService 子接口:负责线程的调度

* |–ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService

5.2.2 ThreadPoolExecutor

image-20210803171646923

5.2.3 ThreadPoolExecutor 的构造方法

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
BlockingQueue<Runnable> workQueue) {
 	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 				Executors.defaultThreadFactory(), 
	defaultHandler);
}
  1. corePoolSize:指定了线程池中的线程数量。

  2. maximumPoolSize:指定了线程池中的最大线程数量。

  3. keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多次时间内会被销毁。

  4. unit:keepAliveTime 的单位。

  5. workQueue:任务队列,被提交但尚未被执行的任务。

  6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。

  7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。

5.2.3.1 拒绝策略

线程池中的线程已经用完了,无法继续为新任务服务。同时等待队列也已经排满了,再也塞不下新任务了。这

时候我们就需要拒绝策略机制合理的处理这个问题。

JDK 内置的拒绝策略如下:

  1. AbortPolicy:直接抛出异常,阻止系统正常运行。

  2. CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的 任务。显然这样做不

    会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

  3. DiscardOldestPolicy:丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

  4. DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩

展 RejectedExecutionHandler 接口。

5.3 线程池创建方式

​ Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行

线程的工具。真正的线程池接口是 ExecutorService。

​ 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但是,强烈建议程序员使用

较为方便的工具类 Executors 工厂方法 :

  1. ExecutorService newFixedThreadPool() : 创建固定大小的线程池,可以进行自动线程回收

  2. ExecutorService newCachedThreadPool() : 缓存线程池,线程数量不固定可以根据需求自动的更改数量。

  3. ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程

  4. ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。

  5. WorkStealingPoll():内部会创建 ForkJoinPool,利用 working-stealing 算法,并行地处理任务,不保证处理的

顺序

5.3.1 new FixedThreadPool

​ 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数

nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务, 则在有可用线程之

前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何 线程终止,那么一个新线程将代替

它执行后续的任务(如果需要)。在某个线程被显式地关闭之 前,池中的线程将一直存在。

5.3.2 new CachedThreadPool

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行 很多短期异

步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造 的线程(如果线程可用)。

如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线

程。因此,长时间保持空闲的线程池不会使用任何资源。处理大量短时间工作任务的线程池

试图缓存线程并重用,当无缓存线程可用是,就会创建新的工作线程

如果线程闲置的时间超过了阈值,则会被终止并移除缓存

系统长时间闲置的时候,不会消耗什么资源

5.3.3 new SingleThreadExecutor

Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后

(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

5.3.4 new ScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。定时或者周期性的工作调度

5.3.5 new WorkStealingPoll()

内部会创建 ForkJoinPool,利用 working-stealing 算法,并行地处理任务,不保证处理的顺序

5.4 Java 线程池工作过程

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也

    不会马上执行它们。

  2. 当调用 execute()方法添加一个任务时,线程池会做如下判断:

    a) 如果正在运行的线程数量小于 corePoolSize(线程池的基本大小),那么马上创建线程运行这个任务;

    b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;

    c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize(线程池中允许的最大线程数),

    那么还是要创建非核心线程立刻运行这个任务;

​ d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常

RejectExecutionException。

  1. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  2. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运 行的线程数大于

corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

image-20210804141841589

5.5 线程池的状态

RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务

SHUTDOWN:不在接受新提交的任务,但是可以处理存量任务

STOP:不在接受新提交的任务,也不处理存量任务

TIDYING:所有的任务已经终止

TERMINATED:terminated 方法执行完后进入该状态

image-20210804142012114

5.6 线程池大小的选择

Cup 密集型:线程数=按照核数或者核实+1

IO 密集型:线程数=cpu 核数*(1+平均等待时间/平均工作时间)

5.7 ForkJoinPool 分支/合并框架 工作窃取

5.7.1 Fork/Join 框架

就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任

务运算的结果进行 join 汇总。

Work-stealing 算法:某个线程冲其他队列里窃取任务来执行

image-20210804142224961

image-20210804142153974

5.7.2 Fork/Join 和线程池的区别

Fork/Join 框架采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,

并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如

果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在 fork/join 框架实现

中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚

未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。

/**
 * 求和,1-n的和
 */
public class TestForkJoinPool {
    public static void main(String[] args) {

        // 创建forkjoinpool
        ForkJoinPool pool = new ForkJoinPool();
         // 创建forkjoin的任务
        ForkJoinTask<Long> task = new ForkJoinSum(1L,1000L);

        // 获得运算结果
        Long sum = pool.invoke(task);
        System.out.println(sum);

    }
}
class ForkJoinSum extends RecursiveTask<Long> {

    private  long start;
    private  long end;

    private  long flag = 500; // 临界值,任务拆分到多个以后就不再拆分

    public ForkJoinSum(long start, long end) {
        this.start=start;
        this.end=end;
    }

    @Override
    protected Long compute() {
        long length = end -start;
        if (length<=flag){   // 没有到临界值的不用分任务计算
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum+=i;
            }
            return  sum;
        }else{  // 到达临界值
            long middle = (start+end)/2;
            ForkJoinSum left = new ForkJoinSum(start, middle);
            ForkJoinSum right = new ForkJoinSum(middle + 1, end);
            left.fork();
            right.fork();
            return left.join()+right.join();
        }

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值