Java 线程池 简化汇总

线程核心类 ThreadPoolExecutor

构造方法如下

    public ThreadPoolExecutor(
        int corePoolSize, 
        int maximumPoolSize,
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue workQueue, 
        ThreadFactory threadFactory, 
        RejectedExecutionHandler handler) {
                
        }

一共四种构造方法,上面的代码是最后一种

前三构造方法个依次是

1.不传后两个参数  threadFactory 和 handler

2.不传最后一个参数  handler

3.不传倒数第二个参数  threadFactory

一 、构造方法所有参数详解:

1、corePoolSize(核心线程数):

向线程池提交任务时,优先创建线程并执行,也可通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

2、maximumPoolSize(线程池最大容量):

当核心线程数无空闲线程,并且任务队列已满。会创建新的线程,这个参数限制了

核心线程+非核心线程的总数

3、keepAliveTime:(线程存活保持时间):

非核心线程,如空闲时间达到这个标准,会被回收

4、TimeUnit (存活保持时间的单位)

5、workQueue:(任务队列):

用于传输和保存等待执行任务的阻塞队列。

6、threadFactory(线程工厂):

用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7、handler(线程饱和策略):

当线程池和队列都满了,再加入线程会执行此策略。

二 、线程池任务执行流程:

我画了一个草图,调用execute()时具体流程:

提交 → 核心线程判断 → 任务队列判断 → 最大线程数判断 → 饱和策略

Java 线程池 简化汇总_第1张图片

三、系统提供的4种线程池:

基本创建:

    /*
     *作者:赵星海
     *时间:2020/8/12 9:41
     *用途: 线程池
     */
    public void testThreadPool() {
        //自定义线程池
        ThreadPoolExecutor threadPoolExecutor = new
                ThreadPoolExecutor(0,0,0,
                TimeUnit.DAYS,null, (RejectedExecutionHandler) null);
        //四种线程池
        Executors.newCachedThreadPool();//高速缓存
        Executors.newFixedThreadPool(5);//固执的    #参数:核心线程数&线程总数
        Executors.newSingleThreadExecutor();//单一的
        Executors.newScheduledThreadPool(5);//预定时间    #参数:核心线程数
    }

1.四种线程池的构造区别

(1)CachedThreadPool  

  可以无限创建线程的线程池,空闲线程可存活时间是 60 s

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

(2)FixedThreadPool  

  有固定大小的线程池。所有线程均为核心线程,线程理论上一直存活。

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

(3)SingleThreadExecutor

  固定只创建使用一个为核心线程,线程理论上一直存活。后面的任务无线排队

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

(4)ScheduledThreadPool

 用来执行 定时 或者 周期性任务

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

 

2.所有队列区别

ArrayBlockingQueue:有界队列(数组结构)

LinkedBlockingQueue:默认无界(链表结构)内部由单链表实现,只能从head取元素,从tail添加元素。添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。

PriorityBlockingQueue:无界 (基于数组的平衡二叉堆)(优先级屏蔽队列)

SynchronousQueue:没实际存储空间,它的工作只有一条,将任务交给闲下来的旧线程执行,或者创建新线程来执行

DelayedWorkQueue: 无界,用数组实现堆排序,具体原理可以看这个https://www.jianshu.com/p/587901245c95

3.线程池如何复用,如何回收

复用核心: 线程执行完任务后会再次循环会调用getTask获取下一个任务

回收核心:getTask→workQueue.poll() 获取不到任务的时候会销毁线程,  空闲线程的存活时间决定于poll()方法

详细:

任务进入队列的过程 execute() →addWorker() 然后入队成功

addWorker()中核心代码是 workers.add();

Worker是什么呢?  

    Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this); //得到线程工厂类,创建线程
        }

一个普通的内部类,有两个核心属性 Thread thread 与 Runnable firstTask

它是负责执行任务的执行者,每一个Worker持有一个任务、一个线程

先是设置了 Worker 的State为 -1,是为了避免当前 worker 在调用 runWorker 方法前被中断(当其它线程调用了线程池的 shutdownNow 时候,如果 worker 状态 >= 0 则会中断该线程)。这里设置了线程的状态为 -1,所以该线程就不会被中断了。我们知道当启动一个线程后,native 层C代码会创建一个线程,然后回调到 Runnable的 run方法,所以当Worker对象中的thread被start后,最后也会回调到 run方法。这方面的知识可以查看:https://blog.csdn.net/wuqiqi1992/article/details/107878581

addWorker()方法中核心代码片段:  深海考虑这边代码有点多,只摘取了部分源码

 ... 
 Worker w = null; 
 try {
     w = new Worker(firstTask);
     final Thread t = w.thread;
     ...
     if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
 ...

 t.start();  意味着线程启动,线程启动会调用C层的代码,然后C层代码会回调到 Runnable 的 run 方法。

 看一下worker的run方法:

    public void run() {
            runWorker(this);
        }
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
//status设置为0,允许中断
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
//执行任务之前做的事情
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
//任务执行,这里会调用run方法,这是真正执行任务的地方
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
//执行完成后,做的事情
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
//统计Worker完成的Task
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
//Worker 完成后执行 清理工作
            processWorkerExit(w, completedAbruptly);
        }
    }

  简单描述其过程:

  线程执行完任务后下次循环会调用getTask获取任务 当取不到任务时,他会跳出循环然后等待被回收

  getTask方法中有这么一段核心代码:

    Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
    //这里说明已经超时了
                timedOut = true;

 非核心线程会走poll(),核心线程会走take();  poll方法的两个参数决定了poll的阻塞时间,超时后返回空

4.饱和策略

ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最长等待时间任务,也就是最前面的任务,然后重新提交被拒绝的任务

ThreadPoolExecutor.CallerRunsPolicy:由提交任务的线程去处理该任务

 

 

 

 

你可能感兴趣的:(Java)