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提供了两种实现:
- HashSelector : 子执行器的 index = hash(key) % totalLen (totalLen是执行器的总数量)
- RandomSelector : 随机选择子执行器的 index (ps:采用这种方式不能保证任务有序的执行)
设计原理
主要就是通过加锁去保证添加到任务队列的过程是有序执行(这个过程是极快的,带来的性能损耗微乎其微)。然后后面消费的过程中,由于任务队列里面的任务天然有序,自然而然也就是顺序消费了。
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的,所以可以更改线程池的流程。
具体流程如下:
- 将任务交给核心线程执行
- 如果核心线程满了,创建空闲线程去执行任务,而不是像之前一样把任务丢到任务队列里面得不到执行。
tomcat线程池就是对JDK线程池做了修改,改变了执行流程。因为SpringMVC的请求很多都是IO型请求,例如查询数据库什么的。