自定义线程池
为什么要用到线程池?
线程是一种系统资源,如果是高并发的情况下,一下子来了很多的任务,我为每个任务都创建一个新的线程,那这样压力是很大的。
而且上下文切换越来越频繁,降低我们的性能。所以我们最好的是使用已有线程的潜力来处理任务,而不是创建新的线程去做事。
这里我们可以联想到设计模式中的享元模式的思想(可以了解一下),预先的创建好里线程,使用完毕再归还给连接池,这样节约了连接的创建和关闭。
线程池工作原理图
这个阻塞队列(Blocking Queue)非常重要 它是平衡我们消费者线程和生产者线程之间的桥梁,线程池中的线程对象从阻塞队列中获取(poll)任务,主线程往线程池中添加(put)任务。
使用Java代码模拟线程池工作思路
-
Deque类型(Java中的双向链表,可以拿来当作我们的任务队列)
-
每次只能有一个线程能拿到我们队列头的元素(我们处理任务的逻辑涉及元素删除等操作),所以我们要给队列头上锁,同时队列在添加进去任务的时候也是需要上锁的。
-
阻塞队列是有容量上限的,无限制的添加内存也会吃紧,所以需要提前设置
接下来我们就将由生产者产生的任务对象交给线程池,要是没有线程就创建线程来执行它,要是有线程但是线程不足就把它放到阻塞队列。
创建阻塞队列 BlockingQueue
阻塞队列中的属性及解释
- queue任务队列
- 需要有一把锁,有多个任务需要获取任务,但是我们必须要保证每次只有队列头的元素可以被获取到,同时我们还需要保护队列尾的元素,这里我们使用ReentrantLock比较灵活。
- 我们还需要有两个条件变量,一个针对消费者的,消费者在队列中没有元素的时候就只能等,我们要有一个阻塞等待的队列waitSet(消费者的条件变量)
- 我们的阻塞队列还要有一个容量上限,不然无限的添加任务内存会炸,到达了容量上限的时候,就不能再往里面添加任务了,所以生产者也必须要有一个阻塞队列(生产者的条件变量)和上面的消费者的条件变量都可以使用ReentrantLock创建
这样我们队列中的属性就定义好了
接下来就是我们队列中的方法了
阻塞队列中的方法及其解释
-
take()阻塞获取
获取的时候需要通过锁来保护,所以我们加把锁,如果队列中没有元素,我们就只能等待,进入我们的emptyWaitSet中等待,要是有任务的话,我们就获取队列头的元素并且return,这里先看到put()方法,重新回到这里之后我们由于已经获取到了元素了,所以也需要唤醒fullWaitSet里面的线程,同样的调用fullWaitSet的signal()方法
-
put()阻塞添加
一样的先添加锁,先看队列满不满,要是满的话就到fullWaitSet里面等待,要是还不满的话我们就可以将这个新的元素添加到队列尾部,我们这里添加完了新元素还必须要唤醒这里阻塞在emptyWaitSet里面的线程,所以要调用emptyWaitSet的signal()方法,唤醒里面的线程,这里再看到take()方法
-
size()获取大小
同样的加把锁,然后在trycatch块中return大小就行了
-
poll()带超时的阻塞获取(好处就是带一个超时时间不需要永久的等待)
参数传递给它一个超时时间timeout,一个TimeUnit。这个TimeUnit是JDK5新增的功能,可以在多个时间单位做任意的转换,比如可以将毫秒转成纳秒。我们先将超时时间统一的转换成纳秒,然后这里我们的emptyWaitSet就可以不用无限制的等待了,我们调用的是awaitNanos。每次等待纳秒,但是问题是(我们如果被提前唤醒)我们下次过来还是空的还是要等待这么多纳秒,而我们这里的awaitNanos返回值是要等待的时间-已经等待的时间==剩余的时间,那么我们将它重新赋值给nanos,这样它下次过来就只需要等待上次剩余的nanos了。(详细的结合代码理解)
BlockingQueue 代码
@Slf4j
class BlockingQueue<T>{
//1、任务队列
private Deque<T> queue=new ArrayDeque<>();
//2、锁
private ReentrantLock lock=new ReentrantLock();
//3、生产者条件变量
private Condition fullWaitSet=lock.newCondition();
//4、消费者条件变量
private Condition emptyWaitSet=lock.newCondition();
//5、容量
private int capacity;
public BlockingQueue(int queueCapacity){
this.capacity=queueCapacity;
}
// 带超时的阻塞获取
// 好处就是带了一个时间不需要永久的等待
public T poll(long timeout, TimeUnit unit){
lock.lock();
try{
//将timeout统一转换成纳秒
long nanos=unit.toNanos(timeout);
while(queue.isEmpty()) {
try {
//返回的是剩余时间
if(nanos<=0){
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞获取
public T take(){
lock.lock();
try{
while(queue.isEmpty()) {
try {//有元素的时候这个await里面会被唤醒
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加
public void put(T task){
lock.lock();
try{
while(queue.size()==capacity){//如果队列的size==容量
try {
log.debug("等待加入任务队列 {} ...",task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean offer(T task,long timeout,TimeUnit timeUnit){
lock.lock();
try{
long nanos=timeUnit.toNanos(timeout);
while(queue.size()==capacity){//如果队列的size==容量
try {
log.debug("等待加入任务队列 {} ...",task);
if(nanos<=0){
return false;
}
nanos=fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
//获取大小
public int size(){
lock.lock();
try {
return queue.size();
}finally {
lock.unlock();
}
}
}
BlockingQueue创建好之后,接下来我们就可以定义线程池类ThreadPool了
创建线程池 ThreadPool
线程池中的属性
-
taskQueue 任务队列 (阻塞队列的聚合)
-
workers 线程的集合 (继承Thread类然后创建的集合)
-
coreSize 核心线程数(控制线程池中的线程数量)
-
timeout 超时时间(要是一直没有任务就将这个线程给释放掉)
线程池中的方法
接下来我们就可以将我们的生产者的任务对象交给线程池了,如果没有线程就创建线程,要是线程繁忙就把它放到阻塞队列
-
execute()
当任务数没有超过核心数的时候就直接交给worker执行(创建线程worker),反之则加入任务队列暂存起来。不过要注意我们的workers是非线程安全的,干脆用synchronized给它上锁保证集合workers的安全。
-
run()
当task不为空,执行任务,当task执行完毕,再从任务队列获取任务并执行,主要就是要不断的取寻找任务执行。任务执行完毕之后就将this(当前的worker)从线程池中移除掉。
@Slf4j(topic = "c.ThreadPool")
class ThreadPool{
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers=new HashSet<>();
// 核心线程数
private int coreSize;
// 获取任务的超时时间
private long timeout;
private TimeUnit timeUnit;
// 执行任务
public void execute(Runnable task){
synchronized (workers) {//保证我们这个集合的安全
//当任务数没有超过coreSize时,就直接交给worker对象执行
//如果任务数超过coreSize时,加入任务队列暂存
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}",worker,task);
workers.add(worker);
worker.start();
} else {
// log.debug("加入任务队列 {}",task);
taskQueue.put(task);
//1、死等
//2、带超时等待
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
}
}
}
ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity){
this.coreSize=coreSize;
this.timeout=timeout;
this.timeUnit=timeUnit;
this.taskQueue=new BlockingQueue<>(queueCapacity);
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task){
this.task=task;
}
@Override
public void run() {
//执行任务
//1)当task不为空,执行任务
//2)当task执行完毕,再接着从任务队列获取任务并执行
while(task!=null||(task=taskQueue.take())!=null){
try {
log.debug("正在执行...{}",task);
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task=null;
}
}
synchronized (workers){
log.debug("worker 被移除{}",this);
workers.remove(this);
}
}
}
}
接下来就是编写测试代码了
测试
死等策略(take)下的线程池处理任务
我们首先实例化一个线程池出来,在主线程中向线程池中提交任务。设置好超时时间为1s,循环不断的交给线程池任务(最大15个任务),每次递交任务前先让线程睡会,不至于让阻塞队列一下满了。
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10);
for (int i = 0; i < 15; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}",j);
});
}
}
}
日志打印结果
20:13:10.327 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-0,5,main], com.dyh.TestPool$$Lambda$2/1963387170@50134894
20:13:10.330 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-1,5,main], com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
20:13:10.330 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
20:13:10.330 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@50134894
20:13:10.330 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
20:13:10.330 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669 ...
20:13:11.345 [Thread-0] DEBUG c.TestPool - 0
20:13:11.345 [Thread-1] DEBUG c.TestPool - 1
20:13:11.345 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
20:13:11.345 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc ...
20:13:12.347 [Thread-0] DEBUG c.TestPool - 2
20:13:12.347 [Thread-1] DEBUG c.TestPool - 3
20:13:12.347 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
20:13:12.347 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc
20:13:12.347 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
20:13:13.349 [Thread-1] DEBUG c.TestPool - 5
20:13:13.349 [Thread-0] DEBUG c.TestPool - 4
20:13:13.349 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
20:13:13.349 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
20:13:14.361 [Thread-0] DEBUG c.TestPool - 7
20:13:14.361 [Thread-1] DEBUG c.TestPool - 6
20:13:14.361 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
20:13:14.361 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
20:13:15.361 [Thread-1] DEBUG c.TestPool - 9
20:13:15.361 [Thread-0] DEBUG c.TestPool - 8
20:13:15.361 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
20:13:15.361 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
20:13:16.373 [Thread-0] DEBUG c.TestPool - 11
20:13:16.373 [Thread-1] DEBUG c.TestPool - 10
20:13:16.373 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916
20:13:16.373 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4e515669
20:13:17.373 [Thread-1] DEBUG c.TestPool - 13
20:13:17.373 [Thread-0] DEBUG c.TestPool - 12
20:13:17.373 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc
。。。
这里由于我们是阻塞获取的策略,每次阻塞队列中没有任务就让线程对象在emptyWaitSet中死等,所以有可能会出现主线程卡住的情况。
这就是两种不同的策略,一个是死等(take)我们已经在上面演示过了,还有一个就是带超时的等待poll(该方法也已在阻塞队列中实现)
超时等待(poll)的策略
将Worker中的run方法改写成
@Override
public void run() {
//执行任务
//1)当task不为空,执行任务
//2)当task执行完毕,再接着从任务队列获取任务并执行
while(task!=null||(task=taskQueue.poll(timeout,timeUnit))!=null){
try {
log.debug("正在执行...{}",task);
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task=null;
}
}
synchronized (workers){
log.debug("worker 被移除{}",this);
workers.remove(this);
}
}
日志打印情况
20:13:10.327 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-0,5,main], com.dyh.TestPool$$Lambda$2/1963387170@50134894
20:13:10.330 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-1,5,main], com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
20:13:10.330 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
20:13:10.330 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@50134894
20:13:10.330 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
20:13:10.330 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
20:13:10.331 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669 ...
20:13:11.345 [Thread-0] DEBUG c.TestPool - 0
20:13:11.345 [Thread-1] DEBUG c.TestPool - 1
20:13:11.345 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
20:13:11.345 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916
20:13:11.345 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc ...
20:13:12.347 [Thread-0] DEBUG c.TestPool - 2
20:13:12.347 [Thread-1] DEBUG c.TestPool - 3
20:13:12.347 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
20:13:12.347 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc
20:13:12.347 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
20:13:13.349 [Thread-1] DEBUG c.TestPool - 5
20:13:13.349 [Thread-0] DEBUG c.TestPool - 4
20:13:13.349 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
20:13:13.349 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
20:13:14.361 [Thread-0] DEBUG c.TestPool - 7
20:13:14.361 [Thread-1] DEBUG c.TestPool - 6
20:13:14.361 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
20:13:14.361 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
20:13:15.361 [Thread-1] DEBUG c.TestPool - 9
20:13:15.361 [Thread-0] DEBUG c.TestPool - 8
20:13:15.361 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
20:13:15.361 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
20:13:16.373 [Thread-0] DEBUG c.TestPool - 11
20:13:16.373 [Thread-1] DEBUG c.TestPool - 10
20:13:16.373 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916
20:13:16.373 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4e515669
20:13:17.373 [Thread-1] DEBUG c.TestPool - 13
20:13:17.373 [Thread-0] DEBUG c.TestPool - 12
20:13:17.373 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc
20:13:18.388 [Thread-1] DEBUG c.TestPool - 14
20:13:18.388 [Thread-0] DEBUG c.ThreadPool - worker 被移除Thread[Thread-0,5,main]
20:13:19.391 [Thread-1] DEBUG c.ThreadPool - worker 被移除Thread[Thread-1,5,main]
这次我们发现线程池就成功的完成任务并释放掉池中的线程了。
当任务队列已经满了的情况
观察,我们如果将任务数创建成15个
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10);
我们的线程池两个核心,且队列的容量也是10,所以最大线程池可以容纳12个任务,那么多出来的3个线程该怎么办?看它怎么处理(这里我们要故意让任务处理时间加长,这样就一下执行不完了,可以出现任务队列满的情况)
一下10个加入了,然后主线程就卡住了,因为线程池整个满了
这个对主线程非常不友好,因为整个卡住了。那应该给主线程一个选择机会,是继续等待还是抛出一个异常,这个就是拒绝策略。
拒绝策略
在任务队列中添加带超时时间的阻塞添加方法offer()
//带超时时间的阻塞添加
public boolean offer(T task,long timeout,TimeUnit timeUnit){
lock.lock();
try{
long nanos=timeUnit.toNanos(timeout);
while(queue.size()==capacity){//如果队列的size==容量
try {
log.debug("等待加入任务队列 {} ...",task);
if(nanos<=0){//如果超过了等待时间了那么我们就认为添加失败
return false;
}
nanos=fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
接下来我们分析队列已满的策略(定位到ThreadPool中的execute方法)
// 执行任务
public void execute(Runnable task){
synchronized (workers) {//保证我们这个集合的安全
//当任务数没有超过coreSize时,就直接交给worker对象执行
//如果任务数超过coreSize时,加入任务队列暂存
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}",worker,task);
workers.add(worker);
worker.start();
} else {
// log.debug("加入任务队列 {}",task);
taskQueue.put(task);
//1、死等
//2、带超时等待
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
}
}
}
如果我们把这些策略都给写到ThreadPool中就相当于是写死了,不好根据需求扩展。
//1、死等
//2、带超时等待
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
于是我们可以将这些策略下放到调用者,让调用者(线程池的使用者)决定到底是执行一个什么策略。这里就可以使用我们的设计模式中的策略模式,当队列满时我们要执行的操作将它给抽象成接口中的抽象方法。
RejectPolicy接口
reject()方法
参数需要哪些就看你需要将哪些信息传递给接口,一个BlockingQueue,一个任务
@FunctionalInterface //拒绝策略
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
我们重新回到execute之前的位置
将原来的注释掉然后在任务队列中创建新方法tryPut(),传递拒绝策略rejectPolicy(在this的类中创建这个成员,并在构造函数中进行初始化)
// log.debug("加入任务队列 {}",task);
// taskQueue.put(task);
//1、死等
//2、带超时等待
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
taskQueue.tryPut(rejectPolicy,task);
在任务队列BlockingQueue中创建这个新的方法
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
//判断队列是否满
if(queue.size()==capacity){
//将我们上面execute里面的权力下放给rejectPolicy去处理
rejectPolicy.reject(this,task);
}else{//队列有空闲
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
这个时候我们可以发现测试类中线程池ThreadPool的构造方法已经报错了,我们给它添加拒绝策略。先实现死等的策略。
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
queue.put(task);
});
接下来我们调用流程如下:
execute方法-->tryPut()-->发现队列如果满了就走我们的函数接口的reject方法,回到线程池的构造,我们是将队列和task给赋给它的。执行的是和之前一样的死等的策略。
我们可以发现利用策略模式,让客户端获得了选择拒绝策略的能力。
接下来我们把其它的拒绝策略都演示一下
超时等待
这里由于第一个任务就要执行1s,肯定超时,在时间内失败,返回false
public static void main(String[] args) {
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
//1、死等
// queue.put(task);
//2、带超时等待
queue.offer(task,500, TimeUnit.NANOSECONDS);
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
});
for (int i = 0; i < 15; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}",j);
});
}
}
这里也是没有死等直接就结束了
19:09:54.750 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-0,5,main], com.dyh.TestPool$$Lambda$2/1963387170@50134894
19:09:54.754 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-1,5,main], com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
19:09:54.755 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@50134894
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
19:09:54.755 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669 ...
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669 ...
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@17d10166 ...
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@17d10166 ...
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916 ...
19:09:54.755 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916 ...
19:09:55.770 [Thread-0] DEBUG c.TestPool - 0
19:09:55.770 [Thread-1] DEBUG c.TestPool - 1
19:09:55.771 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
19:09:55.771 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
19:09:56.777 [Thread-1] DEBUG c.TestPool - 3
19:09:56.777 [Thread-0] DEBUG c.TestPool - 2
19:09:56.777 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3b95a09c
19:09:56.777 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
19:09:57.783 [Thread-0] DEBUG c.TestPool - 5
19:09:57.783 [Thread-1] DEBUG c.TestPool - 4
19:09:57.783 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
19:09:57.783 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
19:09:58.788 [Thread-0] DEBUG c.TestPool - 6
19:09:58.788 [Thread-1] DEBUG c.TestPool - 7
19:09:58.788 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
19:09:58.788 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
19:09:59.791 [Thread-1] DEBUG c.TestPool - 9
19:09:59.791 [Thread-0] DEBUG c.TestPool - 8
19:09:59.791 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
19:09:59.791 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
19:10:00.796 [Thread-0] DEBUG c.TestPool - 11
19:10:00.796 [Thread-1] DEBUG c.TestPool - 10
19:10:01.810 [Thread-0] DEBUG c.ThreadPool - worker 被移除Thread[Thread-0,5,main]
19:10:01.810 [Thread-1] DEBUG c.ThreadPool - worker 被移除Thread[Thread-1,5,main]
将超时时间改成1500毫秒
queue.offer(task,1500, TimeUnit.NANOSECONDS);
这下任务就成功的被执行了
19:12:25.483 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-0,5,main], com.dyh.TestPool$$Lambda$2/1963387170@50134894
19:12:25.493 [main] DEBUG c.ThreadPool - 新增 workerThread[Thread-1,5,main], com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
19:12:25.494 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@50134894
19:12:25.494 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@73c6c3b2
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
19:12:25.494 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
19:12:25.495 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
19:12:25.495 [main] DEBUG com.dyh.BlockingQueue - 加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@4e515669
19:12:25.495 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@17d10166 ...
19:12:25.500 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@17d10166 ...
19:12:25.500 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916 ...
19:12:25.516 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@1b9e1916 ...
19:12:25.516 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc ...
19:12:25.531 [main] DEBUG com.dyh.BlockingQueue - 等待加入任务队列 com.dyh.TestPool$$Lambda$2/1963387170@ba8a1dc ...
19:12:26.501 [Thread-0] DEBUG c.TestPool - 0
19:12:26.501 [Thread-1] DEBUG c.TestPool - 1
19:12:26.501 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@7e0b37bc
19:12:26.501 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@64a294a6
19:12:27.504 [Thread-0] DEBUG c.TestPool - 3
19:12:27.504 [Thread-1] DEBUG c.TestPool - 2
19:12:27.504 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@6ae40994
19:12:27.504 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@1a93a7ca
19:12:28.517 [Thread-0] DEBUG c.TestPool - 4
19:12:28.517 [Thread-1] DEBUG c.TestPool - 5
19:12:28.517 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@3d82c5f3
19:12:28.517 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@2b05039f
19:12:29.528 [Thread-1] DEBUG c.TestPool - 7
19:12:29.528 [Thread-0] DEBUG c.TestPool - 6
19:12:29.528 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@61e717c2
19:12:29.529 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@66cd51c3
19:12:30.531 [Thread-0] DEBUG c.TestPool - 9
19:12:30.531 [Thread-1] DEBUG c.TestPool - 8
19:12:30.531 [Thread-0] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4dcbadb4
19:12:30.532 [Thread-1] DEBUG c.ThreadPool - 正在执行...com.dyh.TestPool$$Lambda$2/1963387170@4e515669
19:12:31.533 [Thread-0] DEBUG c.TestPool - 10
19:12:31.533 [Thread-1] DEBUG c.TestPool - 11
19:12:32.535 [Thread-1] DEBUG c.ThreadPool - worker 被移除Thread[Thread-1,5,main]
19:12:32.535 [Thread-0] DEBUG c.ThreadPool - worker 被移除Thread[Thread-0,5,main]
放弃任务执行
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
//1、死等
// queue.put(task);
//2、带超时等待
// queue.offer(task,1500, TimeUnit.NANOSECONDS);
//3、放弃任务执行
log.debug("放弃{}",task);
//4、抛出异常
//5、让调用者自己执行任务
});
就是一个日志打印不采取其它的操作。
抛出异常
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
//1、死等
// queue.put(task);
//2、带超时等待
// queue.offer(task,1500, TimeUnit.NANOSECONDS);
//3、放弃任务执行
// log.debug("放弃{}",task);
//4、抛出异常
throw new RuntimeException("任务执行失败"+task);
//5、让调用者自己执行任务
});
抛出异常的话如果任务2抛出异常那么接下来的任务就都不执行了。
让调用者自己执行任务
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
//1、死等
// queue.put(task);
//2、带超时等待
// queue.offer(task,1500, TimeUnit.NANOSECONDS);
//3、放弃任务执行
// log.debug("放弃{}",task);
//4、抛出异常
// throw new RuntimeException("任务执行失败"+task);
//5、让调用者自己执行任务
task.run();
});
可以看到有几个任务是被我们的主线程执行的
完整代码
package com.dyh;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: pixel-revolve
* @date: 2022/3/11 17:23
*/
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
ThreadPool threadPool=new ThreadPool(2,1000,TimeUnit.MILLISECONDS,10,(queue,task)->{
//1、死等
queue.put(task);
//2、带超时等待
// queue.offer(task,1500, TimeUnit.NANOSECONDS);
//3、放弃任务执行
// log.debug("放弃{}",task);
//4、抛出异常
// throw new RuntimeException("任务执行失败"+task);
//5、让调用者自己执行任务
// task.run();
});
for (int i = 0; i < 15; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}",j);
});
}
}
}
@FunctionalInterface //拒绝策略
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
@Slf4j(topic = "c.ThreadPool")
class ThreadPool{
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers=new HashSet<>();
// 核心线程数
private int coreSize;
// 获取任务的超时时间
private long timeout;
private TimeUnit timeUnit;
private RejectPolicy<Runnable> rejectPolicy;
// 执行任务
public void execute(Runnable task){
synchronized (workers) {//保证我们这个集合的安全
//当任务数没有超过coreSize时,就直接交给worker对象执行
//如果任务数超过coreSize时,加入任务队列暂存
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}",worker,task);
workers.add(worker);
worker.start();
} else {
// log.debug("加入任务队列 {}",task);
// taskQueue.put(task);
//1、死等
//2、带超时等待
//3、放弃任务执行
//4、抛出异常
//5、让调用者自己执行任务
taskQueue.tryPut(rejectPolicy,task);
}
}
}
ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity,RejectPolicy<Runnable> rejectPolicy){
this.coreSize=coreSize;
this.timeout=timeout;
this.timeUnit=timeUnit;
this.taskQueue=new BlockingQueue<>(queueCapacity);
this.rejectPolicy=rejectPolicy;
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task){
this.task=task;
}
@Override
public void run() {
//执行任务
//1)当task不为空,执行任务
//2)当task执行完毕,再接着从任务队列获取任务并执行
while(task!=null||(task=taskQueue.poll(timeout,timeUnit))!=null){
try {
log.debug("正在执行...{}",task);
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task=null;
}
}
synchronized (workers){
log.debug("worker 被移除{}",this);
workers.remove(this);
}
}
}
}
@Slf4j
class BlockingQueue<T>{
//1、任务队列
private Deque<T> queue=new ArrayDeque<>();
//2、锁
private ReentrantLock lock=new ReentrantLock();
//3、生产者条件变量
private Condition fullWaitSet=lock.newCondition();
//4、消费者条件变量
private Condition emptyWaitSet=lock.newCondition();
//5、容量
private int capacity;
public BlockingQueue(int queueCapacity){
this.capacity=queueCapacity;
}
// 带超时的阻塞获取
// 好处就是带了一个时间不需要永久的等待
public T poll(long timeout, TimeUnit unit){
lock.lock();
try{
//将timeout统一转换成纳秒
long nanos=unit.toNanos(timeout);
while(queue.isEmpty()) {
try {
//返回的是剩余时间
if(nanos<=0){
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞获取
public T take(){
lock.lock();
try{
while(queue.isEmpty()) {
try {//有元素的时候这个await里面会被唤醒
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加
public void put(T task){
lock.lock();
try{
while(queue.size()==capacity){//如果队列的size==容量
try {
log.debug("等待加入任务队列 {} ...",task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean offer(T task,long timeout,TimeUnit timeUnit){
lock.lock();
try{
long nanos=timeUnit.toNanos(timeout);
while(queue.size()==capacity){//如果队列的size==容量
try {
log.debug("等待加入任务队列 {} ...",task);
if(nanos<=0){
return false;
}
nanos=fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
//获取大小
public int size(){
lock.lock();
try {
return queue.size();
}finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
//判断队列是否满
if(queue.size()==capacity){
//将我们上面execute里面的权力下放给rejectPolicy去处理
rejectPolicy.reject(this,task);
}else{//队列有空闲
log.debug("加入任务队列 {}",task);
queue.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
}
总结
在上面的例子中我们演示了线程池的工作流程和流程中对象的创建,以及拒绝策略和使用策略模式将拒绝的权力以优雅的方法赋予给主线程。我们的jdk线程池里面也是有线程池的,并且和这个原理一样,知道了线程池的实现原理后对我们未来调用api也有很大的好处。