java 中的池化技术可以在我们开辟新的线程之前提前为我们准备好,从而减少了系统的开销。线程池帮我们对线程统一管理,同时也提高了系统的响应。jdk concurrent 中的 Executors 类为我们提供了创建线程池的方法,简单易用。主要有以下几个方法:

  1. newFixedThreadPool(int nThreads) 固定线程数的线程池;
  2. newSingleThreadExecutor() 只产生单个线程的线程池;
  3. newCachedThreadPool 缓存类型的线程池,线程数量可伸缩,非固定;

先来说下,除了这些 jdk 给我们封装好的方法外,我们可以自己手动创建线程池,自己创建可以加深我们对这部分知识的印象,我们可以通过调用 ThreadPoolExecutor 的构造方法来创建:

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

看下他的重载构造器:

    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;
    }

它的的构造方法中,主要有这么几个重要的参数:

  1. corePoolSize 线程池的核心线程数;
  2. maximumPoolSize 线程池的最大线程数;
  3. keepAliveTime 闲置线程存货时间,超过这个时间将会被回收(超过核心线程数的部分);
  4. unit 时间单位;
  5. workQueue 阻塞队列,当线程数超过核心线程数时,任务将会被放入队列中;
  6. threadFactory 线程工厂,用于创建线程,一般使用它默认的内部类,ThreadPoolExecutor 没有提供该字段的 setter/getter方法;
  7. handler 拒绝策略,当阻塞队列满,并且超过最大线程数时,将会执行拒绝策略的 reject 方法。

    看一下线程池执行线程的 execute 方法:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            
        int c = ctl.get();
        // 如果工作线程数小于核心线程数,调用 addWorker 方法后直接返回
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 超过了核心线程数,优先添加到阻塞队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 阻塞队列满,新建线程(每个线程都是一个 worker)并添加到 workers。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 阻塞队列满,并且线程数大于最大线程数,调用 handler 的 rejectedExecution 方法。
        else if (!addWorker(command, false))
            reject(command);
    }

再来看下线程池的拒绝策略,ThreadPoolExecutor,有个属性 defaultHandler,即默认的拒绝策略。该类型为 AbortPolicy:其 rejectedExecution 方法是抛出异常。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

DiscardPolicy:什么都不做,放弃本次的任务

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }

DiscardOldestPolicy:抛弃前面的任务,把新的任务加入到缓存队列中:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                // 弹出最前面的任务
                e.getQueue().poll();
                // 执行新的任务
                e.execute(r);
            }
        }

CallerRunsPolicy:由调用线程执行 run 方法:

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                // 可以看到只是当前线程调用了 run 方法,并没有新建线程。
                r.run();
            }
        }

小结:

主要简述了线程池常用的创建方法、ThreadPoolExecutor 的构造器及七个参数的意义,同时也阐述了四种拒绝策略的作用及原理。

jiao个朋友
4 声望0 粉丝