阿里大师带你详解Java线程池

前言:线程池技术是经过对线程资源的统一管理来达到对线程资源的重复利用,下降线程频繁建立和销毁的开销。java jdk在java.util.concurrent并发包中有一套现成的对线程池的实现方案,咱们能够直接拿来使用,快速实现多线程并发编程场景。这里对concurrent包中的线程池框架的实现进行一些分析。java

Java线程池使用代码示例编程

public class Test {
    public static void main(String[] args) throws Exception {
        Task task1 = new Task(1);
        Task task2 = new Task(2);

//        ExecutorService normalExecutor = new ThreadPoolExecutor(2, 4, 200, TimeUnit.MILLISECONDS,
//                new ArrayBlockingQueue<Runnable>(5));
//        ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
//        ExecutorService cachedExecutor = Executors.newCachedThreadPool();
        //建立线程池服务
&emsp;&emsp;&emsp;&emsp; ExecutorService executor = Executors.newFixedThreadPool(2);
&emsp;&emsp;&emsp;&emsp;&emsp;//将任务交给线程池执行
        executor.execute(task1);
        executor.execute(task2);
        executor.shutdown();
    }
}

//能够提交给线程池执行的任务类,线程池执行任务时会执行其中的run方法
class Task implements Runnable {
    private int taskNum;

    public Task(int num) {
        this.taskNum = num;
    }

    public void run() {
        System.out.println("开始执行任务:" + taskNum);
        try {
            Thread.currentThread().sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束执行任务:" + taskNum);
    }
}
复制代码

执行结果以下:缓存

从结果能够看出后提交给线程池的任务先执行了。因此执行execute方法时只是将任务提交给线程池去管理,任务的执行顺序是由线程池内部去协调的。bash

java线程池实现原理多线程

java线程池的核心实现类是ThreadPoolExecutor,该类的继承关系以下:并发

最底层其实现的接口Executor的定义以下:框架

public interface Executor {
    void execute(Runnable command);
}
复制代码

能够看到,该接口只有一个方法execute,ThreadPoolExecutor实现该方法后,经过该方法的调用将任务提交给线程池。因此ThreadPoolExecutor.execute里的逻辑就是线程池执行任务的密码所在。函数

这里先关注下ThreadPoolExecutor类中以下几个比较重要的常量工具

//记录当前线程池中工做线程的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//将一个整形的32位分为两部分,高3位和低29位
private static final int COUNT_BITS = Integer.SIZE - 3;
//将整形的低29位用于存储工做线程数,因此可开启的最大线程数为2的29次方
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

//将整形的高3位用于存储线程池的当前状态值
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
复制代码

ThreadPoolExecutor里不少地方使用了相似的位运算的方式进行状态值的存储和逻辑运算来提升运行效率ui

如今看下ThreadPoolExecutor类的构造函数,构造函数的参数揭示了线程池的核心变量:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
复制代码

这里涉及的几个核心变量解释以下:

  • corePoolSize:核心线程数,线程池运行稳定后维持的线程数
  • maximumPoolSize:最大线程数,线程池最多可使用的线程数
  • keepAliveTime:超时时间,线程在该超时时间间隔内从任务队列未获取到任务时,若线程池工做线程数超过corePoolSize,则关闭当前线程,且线程池线程数减1
  • unit:keepAliveTime使用的时间单位
  • workQueue:任务存放的队列
  • threadFactory:线程工厂,线程池使用该工厂类的方法产生线程
  • handler:当线程池中线程数已达最大值,且任务队列已满,没法处理新加入的任务时。由自定义的handler处理该任务

ThreadPoolExecutor.execute对任务的处理流程以下图:

ThreadPoolExecutor.execute的执行示意图以下:

经过ThreadPoolExecutor构造函数的参数,咱们发现若是咱们要经过ThreadPoolExecutor建立一个适合咱们业务场景的线程池,须要对ThreadPoolExecutor的运行原理和几个核心参数有比较深刻的理解。线程池的设计者在这方面也作了必定的考虑,在concurrent包中,提供了一个有用的工具类Executors,这个类提供了一些工厂方法能够帮助咱们简单方便的建立出适用于各类场景的线程池,有些方法就是对ThreadPoolExecutor作了简单的封装。其中,业务上比较经常使用到的获取线程池的工厂方法有以下几个:

//建立固定大小的线程池,在并发任务比较多的场景中比较经常使用
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>()));
}

//建立一个可缓存线程池,队列只能存放一个元素,任务会及时被线程处理,适用于对任务处理的及时性要求比较高的场景
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
复制代码
相关文章
相关标签/搜索