对线程池做一些扩展

134 阅读5分钟

1. 有序线程池

该线程池可以保证提交到线程池的任务严格顺序执行。

快速入门

给任务添加标识

public class TestKeyRunnable implements Runnable, Ordered {

    private final String key;
    private final String id;

    public TestKeyRunnable(String key, String id) {
        this.key = key;
        this.id = id;
    }

    @Override
    public String getKey() {
        return key;
    }

    @Override
    public void run() {
        System.out.println(
            "key:" + key + " id:" + id + " thread:" + Thread.currentThread().getName() + " run"
        );
    }
}

提交到线程池的任务需要实现 Ordered 接口的 getKey()方法,旨在说明该任务的分组(具有相同Key的任务会按照顺序执行,不同key的任务并发执行)

创建 OrderedThreadPool

public static void main(String[] args) {
    int cpuCoreSize = Runtime.getRuntime().availableProcessors();
    OrderedThreadPool orderedThreadPool = new OrderedThreadPool(
        cpuCoreSize, 2 * cpuCoreSize,
        1L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>(100),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy(),
        new HashSelector()
    );
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            TestKeyRunnable task = new TestKeyRunnable("KEY1", i + 1 + "");
            orderedThreadPool.execute(task);
        }
    }).start();

    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            TestKeyRunnable task = new TestKeyRunnable("KEY2", i + 1 + "");
            orderedThreadPool.execute(task);
        }
    }).start();

    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            TestKeyRunnable task = new TestKeyRunnable("KEY3", i + 1 + "");
            orderedThreadPool.execute(task);
        }
    }).start();

    //key:KEY3 id:1 thread:pool-1-thread-1 run
    //key:KEY2 id:1 thread:pool-1-thread-2 run
    //key:KEY1 id:1 thread:pool-1-thread-3 run
    //key:KEY2 id:2 thread:pool-1-thread-2 run
    //key:KEY3 id:2 thread:pool-1-thread-1 run
    //key:KEY2 id:3 thread:pool-1-thread-2 run
    //key:KEY1 id:2 thread:pool-1-thread-3 run
    //key:KEY1 id:3 thread:pool-1-thread-3 run
    //key:KEY1 id:4 thread:pool-1-thread-3 run
    //key:KEY1 id:5 thread:pool-1-thread-3 run
    //key:KEY1 id:6 thread:pool-1-thread-3 run
    //key:KEY1 id:7 thread:pool-1-thread-3 run
    //key:KEY2 id:4 thread:pool-1-thread-2 run
    //key:KEY3 id:3 thread:pool-1-thread-1 run
    //key:KEY2 id:5 thread:pool-1-thread-2 run
    //key:KEY1 id:8 thread:pool-1-thread-3 run
    //key:KEY2 id:6 thread:pool-1-thread-2 run
    //key:KEY3 id:4 thread:pool-1-thread-1 run
    //key:KEY2 id:7 thread:pool-1-thread-2 run
    //key:KEY1 id:9 thread:pool-1-thread-3 run
    //key:KEY2 id:8 thread:pool-1-thread-2 run
    //key:KEY3 id:5 thread:pool-1-thread-1 run
    //key:KEY2 id:9 thread:pool-1-thread-2 run
    //key:KEY1 id:10 thread:pool-1-thread-3 run
    //key:KEY2 id:10 thread:pool-1-thread-2 run
    //key:KEY3 id:6 thread:pool-1-thread-1 run
    //key:KEY3 id:7 thread:pool-1-thread-1 run
    //key:KEY3 id:8 thread:pool-1-thread-1 run
    //key:KEY3 id:9 thread:pool-1-thread-1 run
    //key:KEY3 id:10 thread:pool-1-thread-1 run
}

可以从测试结果上面看到 只要 key 相同。那么就能够严格保持顺序执行。

注意,你这里需要指定 Selector 类型。Selector的作用就是通过key选择合适的子执行器。多个子执行器之间并发执行,key相同的任务会被路由到同一个子执行器顺序执行。

Selector提供了两种实现:

  1. HashSelector : 子执行器的 index = hash(key) % totalLen (totalLen是执行器的总数量)
  2. RandomSelector : 随机选择子执行器的 index (ps:采用这种方式不能保证任务有序的执行

设计原理

oderThreadPool.png

主要就是通过加锁去保证添加到任务队列的过程是有序执行(这个过程是极快的,带来的性能损耗微乎其微)。然后后面消费的过程中,由于任务队列里面的任务天然有序,自然而然也就是顺序消费了。

2. 支持优先级线程池

PriorityThreadPool支持给线程池里面的任务设置优先级。当这些任务都处于任务队列的时候,优先级最高的任务会排在最前面,优先执行

快速入门

public static void main(String[] args) {
    PriorityThreadPool priorityThreadPool =
        new PriorityThreadPool(1, 1, 0, TimeUnit.SECONDS);
    for (int priority = 19; priority >= 0; priority--) {
        final int finalPriority = priority + 1;
        priorityThreadPool.execute(
            () -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                    System.out.println("当前任务优先级 :" + finalPriority);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            },
            priority
        );
    }
    //当前任务优先级 :20
    //当前任务优先级 :1
    //当前任务优先级 :2
    //当前任务优先级 :3
    //当前任务优先级 :4
    //当前任务优先级 :5
    //当前任务优先级 :6
    //当前任务优先级 :7
    //当前任务优先级 :8
    //当前任务优先级 :9
    //当前任务优先级 :10
    //当前任务优先级 :11
    //当前任务优先级 :12
    //当前任务优先级 :13
    //当前任务优先级 :14
    //当前任务优先级 :15
    //当前任务优先级 :16
    //当前任务优先级 :17
    //当前任务优先级 :18
    //当前任务优先级 :19
}

使用方法极其简单,你只需要 创建 PriorityThreadPool。然后提交任务的时候指定优先级即可。

设计原理

设计原理其实很简单,线程池的队列只需要使用支持排序的阻塞队列 PriorityBlockingQueue,然后将线程池任务包装一层即可。

源码:

package com.hdu.priority;

import java.util.Comparator;
import java.util.concurrent.*;

public class PriorityThreadPool extends ThreadPoolExecutor {
    public PriorityThreadPool(int corePoolSize, int maximumPoolSize,
                              long keepAliveTime, TimeUnit unit
                             ) {
        super(
            corePoolSize, maximumPoolSize,
            keepAliveTime, unit,
            new PriorityBlockingQueue<>(100, new PriorityRunnableComparator())
        );
    }

    public void execute(Runnable runnable, int priority) {
        execute(PriorityRunnable.of(runnable, priority));
    }

    private static class PriorityRunnableComparator implements Comparator<Runnable> {

        @Override
        public int compare(Runnable o1, Runnable o2) {
            if (o1 instanceof PriorityRunnable && o2 instanceof PriorityRunnable) {
                return ((PriorityRunnable) o1).getPriority() - ((PriorityRunnable) o2).getPriority();
            } else {
                return 0;
            }
        }
    }
}


package com.hdu.priority;


public class PriorityRunnable implements Runnable {


    private final int priority;
    private final Runnable original;

    @Override
    public void run() {
        original.run();
    }

    private PriorityRunnable(Runnable runnable, int priority) {
        this.priority = priority;
        original = runnable;
    }


    public static PriorityRunnable of(Runnable runnable, int priority) {
        return new PriorityRunnable(runnable, priority);
    }


    public int getPriority() {
        return priority;
    }
}

3. 更加适合IO型任务的线程池

JDK提供的线程池执行流程是适合于 CPU型任务的线程池。而对于IO型任务,IO型任务是不占用CPU的,所以可以更改线程池的流程。

具体流程如下:

  1. 将任务交给核心线程执行
  2. 如果核心线程满了,创建空闲线程去执行任务,而不是像之前一样把任务丢到任务队列里面得不到执行。

tomcat线程池就是对JDK线程池做了修改,改变了执行流程。因为SpringMVC的请求很多都是IO型请求,例如查询数据库什么的。

4. 源码

K0n9D1KuA (K0n9DiKuA) - Gitee.com