Java线程池ThreadPoolExecutor原理详解

目录

 

前言

线程池的作用

简单实例

参数与原理

线程池的常用创建方式

参考链接


前言

最近java面试,基本都会考察多线程的,多线程就一定要问线程池的,然而我却在同一个问题上栽跟头两次,也是醉醉的。在懊悔之余所以专门花了一个下午的时间把它详细总结整理了一遍,也以此告诫自己学东西切不可浮躁,要静心专研,打扎实基础。

 

问题:

问:新建线程池有哪几个参数,具体含义是什么呢?

问:假如我设置corePoolSize为2,maximumPoolSize为5,现在线程池里已经有1个了,我再往里面添加第2到6个,具体的执行逻辑是什么呢?

问:常用新建线程池的方法有哪几个,他们与ThreadPoolExecutor有关系吗?

线程池的作用

线程虽然提供了并行处理速度,但是线程的新建销毁带来的系统开销是很大滴,为了能够更科学的利用线程,才有了大名鼎鼎的线程池:ThreadPoolExecutor类。作用大致如下:

1、提高资源利用率
线程池可以重复利用已经创建了的线程,线程任务完成后,可以不销毁而是被安排去做别的任务。
2、具有可管理性
程序员可以设置线程个数,线程池会根据系统的使用情况调整运行的线程,降低系统开销。

简单实例

package thread.threadpool;



import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;





/**

 * 线程池的使用

 * @author shu jun

 *

 */

public class ThreadPoolDemo {



    public static void main(String[] args) {

      //1. 新建五个线程

      Thread ta = new MyThread("thread-a");

        Thread tb = new MyThread("thread-b");

        Thread tc = new MyThread("thread-c");

        Thread td = new MyThread("thread-d");

        Thread te = new MyThread("thread-e");

       

        //2. 创建一个可重用固定线程数的线程池

      ThreadPoolExecutor pool =  new ThreadPoolExecutor(2, 2, 0L,

                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 

                new ThreadPoolExecutor.AbortPolicy());

     

        //3. 将线程放入池中进行执行

        pool.execute(ta);

        pool.execute(tb);

        pool.execute(tc);

        pool.execute(td);

        pool.execute(te);



//4. 关闭线程池

        pool.shutdown();

    }

}



class MyThread extends Thread {



   public MyThread(String threadName) {

      this.setName(threadName);

   }

  

    @Override

    public void run() {

        System.out.println(Thread.currentThread().getName() + ", " + this.getName()+ " is running.");

    }

}

 

输出如下:

参数与原理

ThreadPoolExecutor有4个构造函数,最全的就是上面代码中的七个构造参数,其它构造函数只是默认给出参数而已。

corePoolSize

核心线程池的线程数量

maximumPoolSize

最大的线程池线程数量

keepAliveTime

空闲线程活动保持时间,要当 线程数>corePoolSize时才起作用。

unit

线程活动保持时间的单位。

workQueue

指定任务队列所使用的阻塞队列

ThreadFactory

线程工厂,用来创建线程

RejectedExecutionHandler

队列已满,而且任务量大于最大线程的异常处理策略

 

线程往线程池里面丢,最后执行调用pool.execute(Thread); excute函数的核心逻辑如下:

 

Jdk excute源码如下:

public void execute(Runnable command) {

    // 如果任务为null,则抛出异常。

    if (command == null)

        throw new NullPointerException();

    // 获取ctl对应的int值。该int值保存了"线程池中任务的数量"和"线程池状态"信息

    int c = ctl.get();

    // 1. 当线程池中的任务数量 < "核心池大小"时,即线程池中少于corePoolSize个任务。

    // 则通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。

    if (workerCountOf(c) < corePoolSize) {

        if (addWorker(command, true))

            return;

        c = ctl.get();

}



    // 2. 当线程池中的任务数量 >= "核心池大小"时,

    // 2.1 而且,"线程池处于允许状态"时,则尝试将任务添加到阻塞队列中。

    if (isRunning(c) && workQueue.offer(command)) {

        // 再次确认“线程池状态”,若线程池异常终止了,则删除任务;然后通过reject()执行相应的拒绝策略的内容。

        int recheck = ctl.get();

        if (! isRunning(recheck) && remove(command))

            reject(command);

        // 否则,如果"线程池中任务数量"为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。

        else if (workerCountOf(recheck) == 0)

            addWorker(null, false);

}



    //通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。

    // 3. 如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。

    else if (!addWorker(command, false))

        reject(command);

}

 

  1. 这一步很好理解的,如果"线程池中任务数量" < "核心池大小"时,即线程池中少于corePoolSize个任务;此时就新建一个线程,并将该任务添加到线程中进行执行。
  2. 当线程池中的任务数量 >= "核心池大小"时,那么就跟阻塞队列workQueue相关了。来一个线程就丢到workQueue(这个阻塞队列大小可以自由设置)里面去,如果队列容量很大很大,那就没maximumPoolSize啥事了,一直往里面放就可以了,但是这样是很消耗系统资源滴,所以阻塞队列经常是有界的,如果满了,那就继续第3步。
  3. 队列满了就去和maximumPoolSize判断,小于等于则创建新线程,大于则不完了,按照拒绝策略RejectedExecutionHandler,执行reject。具体逻辑在这个比较复杂的addWorker函数中,作用可以理解为添加任务到阻塞队列;
private boolean addWorker(Runnable firstTask, boolean core) {

           … 此处省略若干字…

          for (;;) {
            // 获取线程池中任务的数量。
            int wc = workerCountOf(c);
            // 如果"线程池中任务的数量"超过限制,则返回false。
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
         …
}
原理基本上就是以上三步了。
 
剩下的三个参数中keepAliveTime和unit比较好理解,那么ThreadFactory呢?

ThreadFactory是一个线程工厂,应该说的是线程的创建方式吧,默认是用defaultThreadFactory()方法返回DefaultThreadFactory对象,里面用newThread()来创建的线程,这么搞出来的线程对应的任务是Runnable对象,它创建的线程都是“非守护线程”而且“线程优先级都是Thread.NORM_PRIORITY”。

 

拒绝策略RejectedExecutionHandler具体又分为:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

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

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

 

阻塞队列BlockingQueue还是很有内容的,要总结拓展的话可以挖掘出蛮多好玩的,大致分为三类:
1. 直接提交, SynchronousQueue。

它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。

2. 无界队列, LinkedBlockingQueue。

LinkedBlockingQueue默认为空,则是无界的了,当然也可以传入参数指定线程大小的。

3.有界队列,ArrayBlockingQueue

这又点像LinkedList和ArrayList的感觉,数据存储结构不一样。
 

线程池的常用创建方式

常见三种方式:

  • Executors.newCachedThreadPool():无限线程池。
  • Executors.newFixedThreadPool(nThreads):创建固定大小的线程池。
  • Executors.newSingleThreadExecutor():创建单个线程的线程池。

查看源码发现其实它们调用的都是ThreadPoolExecutor构造函数。

 

类之间的继承关系如下:

那三个常用方法都是Executors的static方法,而又去调用ThreadPoolExcutor;

public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>());

    }



public static ExecutorService newFixedThreadPool(int nThreads) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue<Runnable>());

    }



public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>(),

                                      threadFactory);

}

 

可以把上面第2小节的例子修改下 , 可以测试不同的线程池新建方式,      

 //2. 创建一个可重用固定线程数的线程池

      /*ThreadPoolExecutor pool =  new ThreadPoolExecutor(2, 2, 0L,

                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 

                new ThreadPoolExecutor.AbortPolicy());*/

       

        // 无限线程池

        //ExecutorService pool =  Executors.newCachedThreadPool();

        // 创建固定大小的线程池,与上面一开始面创建ThreadPoolExecutor是等效滴

        ExecutorService pool =  Executors.newFixedThreadPool(2);

        // 创建单个线程的线程池

        //ExecutorService pool =  Executors.newSingleThreadExecutor();

参考链接

JAVA 之ThreadPoolExecutor线程池原理及其execute方法实例详解_怪我咯_php中文网

Java多线程系列--“JUC线程池”03之 线程池原理(二)_skywang12345_cnblogs

线程池BlockingQueue详解_零度anngle_csdn

BlockingQueue的三种实现区别_Men-DD_csdn

 

线程池真是博大精神,还有不少可拓展研究的,不过主体脉络应该就这些了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电商架构修炼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值