Java中的线程池一

2020-07-20  本文已影响0人  明翼

不读书的人,思想就会停止 !

0. 思维导图

线程池是很多编程语言中都有的,虽然我现在很少写java了,但是对于面试或其他语言,其实都是相通的。
这篇是聊下线程的基本内容,下面是基本的思维导图。


线程池

1. 为什么需要线程池

任何程序,都是一些线程,执行一些任务组成的。任务会很多,如果我们每个任务都创建一个线程去执行。那么随着任务的增加,创建的线程数激增,会有很多问题:

  1. 系统可以创建的线程数不是无限的,因为线程需要占用内存等资源信息。
  2. 不是线程越多越好,线程越多,cpu的核数是固定的,那么多个线程切换的时候,就会发生上下文切换影响性能。
  3. 线程创建和销毁本身也有开销,可能线程创建和销毁的时间比执行任务的时间还长,得不偿失。
public class OneTask { 
 
    public static void main(String[] args) { 
        Thread thread = new Thread(new Task());
        thread.start();
    } 
 
    static class Task implements Runnable { 
        @Override
        public void run() { 
           System.out.println("Thread Name: " + Thread.currentThread().getName());
        } 
    } 
}

如果只有一个线程执行任务那,又存在着cpu利用不充分的问题,比如我们很多任务可能需要IO,在等待IO的时候,线程sleep会让出cpu,从而不利于程序整体性能提升。

一个任务一个线程,系统执行慢;每个任务一个线程,又导致线程过多,导致一系列上述问题。

2. 线程池

为了让系统执行的足够快,又不想创建过多线程,占用过多资源,线程池就应运而生。
线程池有以下好处:

  1. 线程池,故名思意,池子里面创建了一定的线程数,任务来了之后,直接运行,提升响应速度。
  2. 线程池有不少线程是长期存在的,那就不需要过多的线程创建和销毁的开销。
  3. 线程池统筹cpu和内存的使用,线程不够的时候创建,线程空闲下来的时候可以进行销毁,在系统性能和资源占用中保持一个平衡。

3. Java线程池参数说明

这些也是面试中常问的问题,Java中线程池的参数说明如下:

3.1 线程池中线程数

Java中的线程数开始的时候是0 ,当添加任务的时候,首先查看线程池中线程数是否达到了核心线程数(corePoolSize),没有则创建; 如果达到了核心线程数,则看下队列是否满了,如果队列也满了,则判断限制线程数是否达到了最大线程数(maxPoolSize),如果没达到,则继续创建线程;如果达到了,则按照Handler处理拒绝任务的方法来处理。

线程池

从这个图需要注意的:

  1. 线程池初始的状态下是没有线程的,直到来了一个任务。
  2. 非核心线程数的创建是在队列满了之后再创建,如果使用无界队列,比如LinkedBlockingQueue则队列永远不会满,也就不会创建非核心线程,线程池的最大线程数只能为corePoolSize 。

3.2 keepAliveTime

keepAliveTime和时间单位决定了非核心线程的存活时间,如果线程池的线程超过核心线程数,会根据这个时间来销毁非核心线程数,从而减少空闲时候的资源占用;任务多的话,又会根据上图规则来继续创建非核心线程。具备线程数弹性变化的能力。

3.3 ThreadFactory

创建线程的线程工厂,我们如果需要定制线程,比如设置线程的名称等,可以自定义线程工厂来替换默认的线程工厂来创建线程。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

3.4 workQueue

Java线程池中的任务队列,常用的几种:

LinkedBlockingQueue     

没有容量限制的队列,也真是因为无限,则有可能造成OOM问题。

SynchronousQueue

这个队列很奇怪,没有空间存放数据,来了任务直接交给线程,不进行存放,性能好。也许会奇怪,为啥要又这样队列,我觉得解耦是一方面,解耦生产者和消费者,比如solr的客户端我记得再6.3版本的时候就是用这种队列来提交任务的。

DelayedWorkQueue       

延迟任务队列,当我们做任务定时调度的时候需要,使用此队列。采用堆这种数据结构,可以保持先执行的任务放在队头。
这个队列也是无界队列,也容易引起OOM问题。

四 线程拒绝任务

线程在两种情况下会拒绝任务:

  1. 线程池已经调用shutdown了,相当于已经关闭了线程池,当然再来的任务会被拒绝掉。
  2. 按照刚才线程池中线程的创建过程描述,当线程已经无法再创建非核心线程的时候,再来的任务就会被拒绝掉。

拒绝又分几种拒绝策略:

  1. AbortPolicy: 拒绝抛异常RejectedExecutionException
    这是线程池默认的拒绝策略,如果需要拒绝,则抛出运行时异常:RejectedExecutionException 调用者自行决定如何处理。

  2. DiscardPolicy: 这个比较狠,直接默默地抛弃,不通知,容易丢失任务。

  3. DiscardOldestPolicy: 这个是删除在队列中存活最久的任务,腾出空间给新来的任务,也是默默地抛弃,无提示。

  4. CallerRunsPolicy: 这个是我觉得比较好的策略,它不拒绝,如果满足拒绝条件后,哪个线程执行提交任务,就在哪个线程执行这个任务。你不是提交快慢,那我就交给你执行,这样可以防止提交太快导致无法执行的问题,线程执行了任务就会减慢了提交任务的速度,给线程池执行腾出了时间。

上一篇下一篇

猜你喜欢

热点阅读