Java并发编程学习(10):线程池、饥饿现象

自定义线程池

class ThreadPoolTest{
    public static void main(String[] args){
        RejectPolicy<Runnable> rejectPolicy = BlockingQueue::put;
        MyThreadPool pool = new MyThreadPool(3, 1000, TimeUnit.MILLISECONDS, 4, rejectPolicy);
        for (int i = 0; i < 30; i++) {
            int j = i;
            pool.execuct(() -> {
                log.info("i = {}", j);
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

        }
    }
}

interface RejectPolicy<T>{
    void reject(BlockingQueue<T> queue, T task);
}

public class MyThreadPool {
    // 任务队列
    private BlockingQueue<Runnable> blockingQueue;

    // 工作队列
    private final HashSet<Worker> workers = new HashSet<>();

    // 核心线程数
    private int coreSize;

    // 等待任务的超时时间
    private long timeout;
    private TimeUnit timeUnit;

    // 拒绝策略
    private RejectPolicy<Runnable> rejectPolicy;

    public MyThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.blockingQueue = new BlockingQueue<>(queueCapcity);
        this.rejectPolicy = rejectPolicy;
    }

    public void execuct(Runnable task){
        // 当任务数没有超过coreSize时,可以直接交给worker对象执行
        synchronized (workers) {
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.info("新增worker:{}",worker);
                workers.add(worker);
                worker.start();
            } else {
                // 当任务数超过coreSize时,加入任务队列暂存
                blockingQueue.tryPut(task,rejectPolicy);
            }
        }
    }

    class Worker{
        private Thread thread;
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
            this.thread = new Thread(logicTask());
        }

        public void start(){
            thread.start();
        }

        private Runnable logicTask(){
            return () -> {
                // task不为空,执行任务
                // task为空,但是队列中有任务,将队列中的任务拿出来赋给task,在判断其是否为空
                while (task != null || (task = blockingQueue.poll(timeout,timeUnit) )!=null){
                    try {
                        log.info("正在执行任务:{}",task);
                        task.run();
                    }catch (Exception e){

                    }finally {
                        task = null;
                    }
                }
                // 不再执行任务,需要将其移出workers
                synchronized (workers){
                    workers.remove(this);
                }
                log.info("worker运行结束:{}",this);
            };
        }
    }

}

class BlockingQueue<T>{

    // 容量
    private int capacity;

    // 队列
    private Deque<T> queue = new ArrayDeque<>();;

    // 锁
    private ReentrantLock lock = new ReentrantLock();

    // 生产者信号量
    private Condition fullCondition = lock.newCondition();

    // 消费者信号量
    private Condition emptyCondition = lock.newCondition();

    public BlockingQueue(int capacity) {
         this.capacity = capacity;
    }

    /**
     * 无时间限制的等待
     * @return
     */
    public T poll(){
        lock.lock();
        try{
            while (queue.isEmpty()){
                try {
                    emptyCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T pop = queue.pop();
            fullCondition.signal();
            return pop;
        }finally{
            lock.unlock();
        }
    }

    /**
     * 有时间限制的等待
     * @param time 等待时间
     * @param unit 时间单位
     * @return
     */
    public T poll(long time, TimeUnit unit){
        lock.lock();
        try{
            long nanos = unit.toNanos(time);
            while (queue.isEmpty()){
                try {
                    if (nanos <= 0){
                        return null;
                    }
                    nanos = emptyCondition.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T pop = queue.pop();
            fullCondition.signal();
            return pop;
        }finally{
            lock.unlock();
        }
    }

    public void tryPut(T task, RejectPolicy<T> rejectPolicy){
        lock.lock();
        try{
            // 队列是否已经满了
            if (queue.size() == capacity){
                rejectPolicy.reject(this,task);
            }else {
                // 队列有空位
                log.info("加入任务队列:{}",task);
                queue.addLast(task);
                emptyCondition.signal();
            }
        }finally{
            lock.unlock();
        }
    }

    /**
     * 放入任务
     * @param task 待放入的任务
     */
    public void put(T task){
        lock.lock();
        try{
            while (queue.size() >= capacity){
                try {
                    log.info("等待加入任务队列:{}",task);
                    fullCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("加入任务队列:{}",task);
            queue.addLast(task);
            emptyCondition.signal();
        }finally{
            lock.unlock();
        }
    }

    public boolean put(T task, long timeout, TimeUnit timeUnit){
        lock.lock();
        try{
            long nanos = timeUnit.toNanos(timeout);
            while (queue.size() >= capacity){
                try {
                    log.info("等待加入任务队列:{}",task);
                    if (nanos <= 0){
                        return false;
                    }
                    nanos = fullCondition.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("加入任务队列:{}",task);
            queue.addLast(task);
            emptyCondition.signal();
            return true;
        }finally{
            lock.unlock();
        }
    }

    public int size(){
        lock.lock();
        try{
            return queue.size();
        }finally{
            lock.unlock();
        }
    }

}

JDK线程池

JDK线程池继承/实现关系

JDK线程池继承/实现关系如下图所示,其中:

  • Executor : 执行器接口,可以用来执行提交的Runnable任务
  • ExecutorService:执行服务器接口,可以用来调度执行已经提交一系列任务
  • AbstractExecutorService:抽象执行服务器类,实现了ExecutorService中一些通用的功能,如非空判断等
  • ThreadPoolExecutor:线程池执行器,JDK提供的线程池的经典实现
  • ScheduledExecutorService:定时执行服务器接口,可以用来执行一些定时任务
  • ScheduledThreadPoolExecutor:定时任务线程池执行器

JDK线程池继承/实现关系

ThreadPoolExecutor的使用

线程池状态

状态名高 3位接收新任务处理阻塞队列任务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余任务
STOP001NN会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入终结
TERMINATED011--终结状态

从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING

这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 CAS 原子操作进行赋值。

// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }

构造方法

ThreadPoolExecutor的完整构造方法如下

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
  • corePoolSize 核心线程数目 (最多保留的线程数)
  • maximumPoolSize 最大线程数目
  • keepAliveTime 生存时间 - 针对救急线程
  • unit 时间单位 - 针对救急线程
  • workQueue 阻塞队列
  • threadFactory 线程工厂 - 可以为线程创建时起个好名字
  • handler 拒绝策略

核心线程与救急线程

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  • 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue队列排队,直到有空闲的线程。
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。

拒绝策略

如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现:

  • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
  • CallerRunsPolicy 让调用者运行任务
  • DiscardPolicy 放弃本次任务
  • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  • Dubbo 的实现,扩展了AbortPolicy,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
  • Netty 的实现,是创建一个新线程来执行任务
  • ActiveMQ 的实现,带超时等待(60s)尝试放入队列
  • PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
    当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTimeunit 来控制。

提交任务

线程池除了能够执行Runnable这种无返回值的任务外,可以执行其它Callable任务

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

关闭线程池

关闭线程池存在两种方法,一种时比较温和的shutdown()方法,另一种时比较粗暴的shutdownNow()方法。
shutdown()
执行shutdown()方法会使线程池进入SHUTDOWN状态:

  • 不会接收新任务
  • 但已提交任务会执行完
  • 此方法不会阻塞调用线程的执行

其源码如下:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

shutdownNow()
执行shutdownNow()方法会使线程池进入STOP状态:

  • 不会接收新任务
  • 会将队列中的任务返回
  • 并用 interrupt 的方式中断正在执行的任务

其源码如下:

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

其它常用方法

// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();

// 线程池状态是否是 TERMINATED
boolean isTerminated();

// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

生成ThreadPoolExecutor对象

通常情况下,我们不会直接调用ThreadPoolExecutor的构造方法,而是使用JDKExecutors中提供了众多工厂方法来创建各种用途的线程池。

newFixedThreadPool

其方法如下

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

特点:

  • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  • 阻塞队列采用了LinkedBlockingQueue,它是无界的,可以放任意数量的任务
  • 适用于任务数量已知相对耗时的任务

如果需要自定义线程名,可以采用方法newFixedThreadPool(int nThreads, ThreadFactory threadFactory)并传入线程工厂。

newCachedThreadPool

其方法如下

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

特点:

  • 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着:
    • 全部都是救急线程(60s 后可以回收)
    • 救急线程可以无限创建
  • 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
  • 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。

newSingleThreadExecutor

其方法如下

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

使用场景:

  • 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

与单独开一个线程执行的区别:

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作

newFixedThreadPool(1)的区别:

  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

ScheduledThreadPoolExecutor的使用

线程池ScheduledThreadPoolExecutor是JDK1.5引入的,在此之前,执行定时任务或延时任务都是使用的Timer。但是Timer具备以下缺点:

  • 所有任务都是由同一个线程来调度,因此所有任务都是串行执行的
  • 同一时间只能有一个任务在执行
  • 前一个任务的延迟或异常都将会影响到之后的任务

延时执行

ScheduledThreadPoolExecutor中,执行延时任务可以采用schedule()方法,其中command或者callable是需要执行的任务,delay是延时时间,unit是时间单位。

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit)
                                       
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit)

周期执行

定时执行任务或周期执行任务可以使用scheduleAtFixedRate()scheduleWithFixedDelay()两个方法。这两个方法拥有相似的函数签名,但是他们了对周期的控制并不相同。

其中scheduleAtFixedRate()的函数签名如下:其中控制周期的参数为period

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit)

scheduleAtFixedRate()中,任务会依次执行,不会重叠执行,如果其中一次花费的时间较长,导致后面的任务无法按时执行,那么下一次任务会立即启动,直至追上预设的任务周期。
以下面的测试代码为例,task中每打印一次cnt就是使当前线程进入睡眠状态,初始睡眠时间为4秒,并逐次减半。另一方面,我们设置task的执行周期为2秒每次,其周期小于初始睡眠时间。

public static void main(String[] args) {
        ThreadFactory factory = new ThreadFactory() {
            int i = 0;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"线程-" + (i++));
            }
        };
        ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1,factory);

        AtomicLong sleepTime = new AtomicLong(4000);
        AtomicInteger cnt = new AtomicInteger(0);
        log.info("start...");
        Runnable task = () -> {
            log.info("cnt = {}", cnt.incrementAndGet());
            try {
                Thread.sleep(sleepTime.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sleepTime.updateAndGet(v -> v / 2);
            log.info("finish...");
        };
        pool.scheduleAtFixedRate(task,1,2, TimeUnit.SECONDS);
    }

代码打印日志如下,我们可以对其进行进一步分析。

[190 ms] [INFO][main] i.k.e.c.e.t.ScheduledThreadPoolDemo : start...
[1233 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 1
[5245 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[5245 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 2
[7254 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[7254 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 3
[8260 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[8260 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 4
[8766 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[9229 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 5
[9481 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...
[11237 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : cnt = 6
[11363 ms] [INFO][线程-0] i.k.e.c.e.t.ScheduledThreadPoolDemo : finish...

每次任务的启动时间如下边所示,分析得出,整个周期任务在第4次追上了预设时间,第5次往后开始按照预设周期执行。

任务预计启动时间实际启动时间实际结束时间备注
1119012335245睡眠了4秒,导致后面的任务无法按照预计时间执行
2319052457254上一次结束时间超过本次的预计启动时间,需要立即执行
3519072548260同上
4719082608766同上
5919092299481上一次结束时间小于本次的预计启动时间,按计划执行即可
6111901123711363同上

而方法scheduleWithFixedDelay()与此不同,他会在执行完才开始等待,确保每两次任务之间拥有相同的空闲时间。其方法签名如下,其中delay用来控制空闲窗口的大小。

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit)

定时任务

如何让每周五中午12点执行任务?这本质上还是一个周期任务,但是需要计算以下两个数据:

  • initialDelay:当前时间距离下一个周五中午12点还有多久?
  • period:执行的周期是多久?

由于两者共用了同一个时间unit,所以initialDelayperiod需要换算为一致的单位,采用毫秒最为合适。

// 获取当前时间
LocalDateTime now = LocalDateTime.now();

// 设置到本周周五
LocalDateTime target = now.withHour(12).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.FRIDAY);
// 如果目标时间比当前时间小,则需要在目标时间上加1周
if (now.isAfter(target)){
    target = target.plusWeeks(1);
}
// 计算两者的差值就是初始延时时间,转化为毫秒
long initialDelay = Duration.between(now, target).toMillis();

// 计算周期:可以使用时间工具类直接将天数转化为毫秒
long period = TimeUnit.DAYS.toMillis(7);

如何正确处理线程池中的异常

任务自身处理异常

在任务最外围使用try-catch代码块,捕获任务中可能出现的,然后将其记录到日志中。

借助Future对象

在下面的代码中,我们向线程池提交了任务task,并拿到了相应的Future对象,在一般情况下,线程正常结束,future.get()会得到返回值,但如果task在执行期间出现异常,future.get()则会抛出ExecutionException异常,我们可以通过捕获该异常来进一步得到线程中实际发生的异常。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int nThreads = 5;
        ThreadFactory factory = new ThreadFactory() {
            int i = 0;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"线程-" + (i++));
            }
        };
        ExecutorService executor = new ThreadPoolExecutor(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                factory);
        Callable<Boolean> task = () -> {
            log.info(Thread.currentThread().getName());

            return true;
        };

        Future<Boolean> future = executor.submit(task);
        log.info("ret : {}",future.get());
    }

工作线程

定义

让有限的工作线程来轮流异步处理无限多的任务。它的典型实现就是线程池,也体现了经典设计模式中的享元模式。

注意:不同任务类型应该使用不同的线程池,这样能够避免饥饿,并提升效率。

饥饿

现象

固定大小的线程池会有饥饿现象:

  • 两个工人是同一个线程池中的两个线程
  • 它们要做的事:给客人点餐和到后厨做菜,这是两个阶段的工作
    • 客人点餐:必须先点餐,等餐做好,才能上菜,在此期间工人必须等待
    • 后厨做菜:只需要做菜即可
  • 当来了一个客人时,工人A去点餐,工人B去做菜
  • 当来了两个客人时,他们都去点餐了,没人做菜,造成饥饿

造成该现象的原因:固定大小的线程池会数显线程数量不足的情况

解决

方案1:增大线程池大小,但并非根本解决之道,因为如果之后工作密度增大,还是会出现饥饿现象。

方案2:不同任务类型应该使用不同的线程池。

线程池大小的设定

线程池过小:线程池吞吐量过小,处理速度较慢。
线程池过大:频繁进行上下文切换会产生大量额外的开销,同时过多的线程会占用大量的内存。

CPU密集型运算

应用程序主要用来进行计算和数据分析,此时最佳值为: CPU核心数 + 1。
此外,+ 1 是为了保障如果当前线程因为操作系统的故障导致线程暂停,额外的线程能够顶上去,保证CPU时钟周期不被浪费。

I/O密集型运算

I/O密集型的运算中,CPU不总是处于繁忙状态,例如,当你进行IO操作,包括进行数据操作和RPC的调用时,CPU就闲下来了,因此可以利用多线程提高它的利用率。
经验公式如下:

线程数 = 核数 * 期望 CPU利用率 * (CPU计算时间 + CPU等待时间)/ CPU计算时间 

时间占比如何估算:运行程序,利用分析工具进行统计,不需要太精确

Tomcat中的线程池

Tomcat有两大核心组件:连接器Connector和容器Container。Tomcat在Connector使用了线程池,具体示意图如下:

Connector(NIO EndPointer)
Executor
有读
有读
sockerProcessor
sockerProcessor
Acceptor
LimitLatch
SocketChannel1
SocketChannel2
Poller
worker1
worker2
  • LimitLatch 用来限流,可以控制最大连接的个数,类似JUC中的Semaphore
  • Acceptor 只负责接收新的Socket连接
  • Poller 只负责监听socket channel中是否有可读的I/O事件
  • 一旦可读,Poller 会封装一个任务对象sockerProcessor,提交给Executor线程池处理
  • Executor线程池中的工作线程负责最终的请求处理

可见:合理的分工是实现高性能的保障

Tomcat中线程池的实现

Tomcat中线程池扩展了ThreadPoolExecutor,其行为稍有不同,主要体现在拒绝策略上
如果线程池中的线程数达到了maximumPoolSize,它不会立刻抛出异常,而是会再次尝试将任务放入队列,如果仍然失败,才会抛出RejectedExecutionException

Tomcat线程池的配置

Connector配置

对应文件server.xml中Connector标签。

配置项默认项说明
acceptorThreadCount1Acceptor线程数量,没有必要不需要调大,因为大部分情况下处于阻塞状态,因为没有新的连接
pollerThreadCount1Poller线程数量,没有必要不需要调大,Poller采用NIO多路检测与复用的思想
minSpareThreads10核心线程数
maxThreads200最大线程数
executor-Executor名称,用来引用对应的Executor配置,如果定义了,则会覆盖上面的minSpareThreads和maxThreads

Executor配置

对应文件server.xml中Executor标签,默认是被注释掉的。

配置项默认项说明
threadPriority5线程优先级
daemontrue是否为守护线程
minSpareThreads25核心线程数
maxThreads200最大线程数
maxIdleTime60000救急线程的生存时间,单位为毫秒,即默认值为1分钟
maxQueueSizeInteger.MAX_VALUE阻塞队列长度,如果将来服务器压力很大,可能造成任务的堆积,此时应该调整为一个合适的值
prestartminSpareThreadsfalse核心线程是否在服务器启动时初始化

Fork/Join线程池

概念

Fork/Join线程池是在JDK1.7加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的CPU密集型运算。
所谓任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解,跟递归相关的一些计算,如归并排序、斐波那契数列,都可以使用分治思想求解。
Fork/Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升运算效率。
Fork/Join默认会创建于CPU核心数大小相同的线程池。

使用

提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务。

class SumTask extends RecursiveTask<Integer> {

    int left;
    int right;

    public SumTask(int left, int right) {
        this.left = left;
        this.right = right;
    }

    @Override
    protected Integer compute() {
        // 如果 left == right,可以求得结果了
        if (left == right) {
            log.debug("join() {}", left);
            return left;
        }
        int mid = left + (right - left) / 2;
        // 将任务进行拆分(fork)
        SumTask t1 = new SumTask(left, mid);
        SumTask t2 = new SumTask(mid + 1, right);
        t1.fork();
        t2.fork();
        log.debug("fork() {} + {}", t1, t2);
        // 合并(join)结果
        int result = t1.join() + t2.join();
        log.debug("join() {} + {} = {}", t1, t2, result);
        return result;
    }

    @Override
    public String toString() {
        return String.format("{%d, %d}", left, right);
    }
}

对任务定义完成后,可以向ForkJoinPool对象提交任务, 并得到返回值。使用方法如下:

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Integer ret = pool.invoke(new SumTask(1,6));
        System.out.println("ret = " + ret);
    }

打印日志如下

[179 ms] [DEBUG][ForkJoinPool-1-worker-3] i.k.e.c.e.t.SumTask : fork() {4, 5} + {6, 6}
[180 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : fork() {0, 1} + {2, 3}
[185 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() 0
[179 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : fork() {4, 4} + {5, 5}
[185 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() 1
[179 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : fork() {2, 2} + {3, 3}
[180 ms] [DEBUG][ForkJoinPool-1-worker-4] i.k.e.c.e.t.SumTask : fork() {0, 0} + {1, 1}
[179 ms] [DEBUG][ForkJoinPool-1-worker-1] i.k.e.c.e.t.SumTask : fork() {0, 3} + {4, 6}
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() 4
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() 5
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 2
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 3
[185 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() {2, 2} + {3, 3} = 5
[186 ms] [DEBUG][ForkJoinPool-1-worker-5] i.k.e.c.e.t.SumTask : join() 6
[185 ms] [DEBUG][ForkJoinPool-1-worker-4] i.k.e.c.e.t.SumTask : join() {0, 0} + {1, 1} = 1
[185 ms] [DEBUG][ForkJoinPool-1-worker-6] i.k.e.c.e.t.SumTask : join() {4, 4} + {5, 5} = 9
[186 ms] [DEBUG][ForkJoinPool-1-worker-2] i.k.e.c.e.t.SumTask : join() {0, 1} + {2, 3} = 6
[186 ms] [DEBUG][ForkJoinPool-1-worker-3] i.k.e.c.e.t.SumTask : join() {4, 5} + {6, 6} = 15
[186 ms] [DEBUG][ForkJoinPool-1-worker-1] i.k.e.c.e.t.SumTask : join() {0, 3} + {4, 6} = 21
ret = 21.0

JDK1.8改进

在JDK1.8中,Stream被引入,它可以用来自动拆分工作而不需要我们手动拆分,简化了对任务拆分的要求。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值