听了老师讲了一次,自己又敲了一次,线程池这块自定义一个简单的是可以勉强做到了。
自定义线程池在老师的讲解下,其实共划分了三项,分别为阻塞队列,worker线程派生,以及线程持类。
阻塞队列是一个生产者消费者模式的应用方式,因为它的目的就是为了在核心线程满了时,将内容存放至阻塞队列中(生产者),在阻塞队列满了时,会wait阻塞等待,然后等核心线程的内容执行完成后。阻塞队列的内容被消费。
worker线程,其实就是继承了Thread类,只不过在run方法中,会对任务进行监听,会采用一个for循环,如果当前持有的任务执行完毕后,会从阻塞队列中获取任务,给自己的task变量赋值,直到所有任务完成后,在workList中释放掉任务,线程自动结束。
线程池,其实主要目的就是为了控制线程数量,当线程小于核心线程数时,创建核心线程worker,来执行任务。如果大于核心线程数了,可以按照任务拒绝策略来进行后续执行。这里的拒绝策略将其放到了阻塞队列中。
这个线程类的实现和真正的线程类不一样,官方线程池创建好后不会消亡,而这个线程池只要核心线程任务以及阻塞队列的任务执行完毕后,线程就会消亡。需要看一下官方源码,有了这个思路,也好做多了。
package com.bo.threadstudy.eight;
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.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
* 自定义线程池,考虑一下线程池内部有什么
* 核心线程数
* 最大线程数
* 救急线程存活时间
* 单位
* 阻塞队列
* 线程工厂
* 任务拒绝策略
* 线程池的这种,明白怎么写就行了,内部封装类,继承了线程,封装了线程方法,并在run方法中对阻塞队列进行监控
* 然后是阻塞队列,在阻塞队列中,要对对任务提供生产者消费者模式的方法,并且在容器满了或空了后进行阻塞
* 最后是线程池类,在现在写的这个例子,线程池只是做了个对核心线程数的控制,以及如果超过了核心线程的任务拒绝策略
*
*/
@Slf4j
public class MyThreadPoolTest {
public static void main(String[] args) {
BlockedQueue<Runnable> runnableBlockedQueue = new BlockedQueue<Runnable>(2);
MyThreadPool myThreadPool = new MyThreadPool(1,1,1,TimeUnit.MILLISECONDS,runnableBlockedQueue,o -> {runnableBlockedQueue.prd((Runnable) o);return null;});
AtomicInteger atomicInteger = new AtomicInteger(10);
for (int i = 0; i < 10; i++) {
myThreadPool.execute(() -> {
log.debug(atomicInteger.decrementAndGet()+"");
});
}
}
}
@Slf4j
class MyThreadPool{
//核心线程数
private Integer corePoolSize;
//最大线程数
private Integer maxMumPoolSize;
//救急线程存活时间
private Integer keepaliveTime;
//时间单位
private TimeUnit timeUnit;
//阻塞队列
private BlockedQueue<Runnable> blockedQueue;
//任务拒绝策略
private Function handler;
private HashSet<Worker> workerList = new HashSet<>();
//Worker就是一个线程对象,来实现线程功能
private class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
//对任务来进行执行,在两种场景下执行任务,任务不为空,或者阻塞队列里有任务
while(task != null || (task = blockedQueue.cus()) !=null){
try{
task.run();
}finally {
//在任务执行完成后将任务置为空
task = null;
}
}
//这个没有问题,因为每次从阻塞队列中拿任务时,都会替换掉task,最终在workList中移除时,只移除掉这个任务即可,然后线程执行结束
synchronized (workerList){
//最后从工作队列中移除执行完成的线程,当前线程对象,这个不是任务
workerList.remove(this);
}
}
}
public MyThreadPool(Integer corePoolSize, Integer maxMumPoolSize, Integer keepaliveTime, TimeUnit timeUnit, BlockedQueue<Runnable> blockedQueue, Function handler) {
this.corePoolSize = corePoolSize;
this.maxMumPoolSize = maxMumPoolSize;
this.keepaliveTime = keepaliveTime;
this.timeUnit = timeUnit;
this.blockedQueue = blockedQueue;
this.handler = handler;
}
//接下来需要怎么做,也是两个方法,获取线程以及返回线程
public void execute(Runnable runnable){
//创建核心线程数
//线程池其实就是一个判断性的业务逻辑,用来对线程池中同时并发的任务信息来进行保障
synchronized (workerList){
if(workerList.size() <corePoolSize){
Worker worker = new Worker(runnable);
workerList.add(worker);
worker.start();
}else{
//接下来就是大于等于核心线程数了,先不考虑最大线程数,在这种场景,先放到阻塞队列里面
//如果放到阻塞队列里面,数量过多,就会发生阻塞现象
// blockedQueue.prd(runnable);
//调用任务拒绝策略
handler.apply(runnable);
}
}
}
}
/**
* 阻塞队列和连接池不一样的地方在于,阻塞是一个真正的生产者消费者模式,而连接池的话,使用完成后不会移除
* @param <T>
*/
@Slf4j
class BlockedQueue<T>{
//阻塞队列需要有什么,就是原先连接池的那几个
private Integer capity;
//默认是无界队列
private Deque<T> deque = new ArrayDeque<>();
//阻塞队列,肯定不是用CAS了,用了我也不咋会
private final ReentrantLock lock = new ReentrantLock();
//条件变量
Condition prdCondition = lock.newCondition();
Condition cusCondition = lock.newCondition();
public BlockedQueue(int capity){
this.capity = capity;
}
//从阻塞队列中获取超时等待
public void prd(T t){
//得到当前长度
lock.lock();
try{
int size = size();
//当前阻塞队列已经存放满了,此时生产者的condition进入await
//奇怪,容量是2的情况下,size是1,怎么会等待
while(size() == capity){
log.debug("生产者size:"+size());
try {
//阻塞
log.debug("生产者等待");
prdCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//阻塞队列数量小于容量时,将信息放入至阻塞队列中
log.debug("生产者将内容放入至阻塞队列");
deque.addLast(t);
log.debug("生产者生产后size:"+size());
cusCondition.signalAll();
}finally {
lock.unlock();
}
}
//消费的话就是从连接里取出一个
public T cus(){
lock.lock();
try{
int size = size();
while (size() == 0){
//阻塞队列没有内容时,需要唤醒生产者
log.debug("消费者size:"+size());
try {
log.debug("消费者等待");
cusCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("消费者将内容移除队列");
log.debug("消费者消费后size:"+size());
prdCondition.signalAll();
return deque.removeFirst();
}finally {
lock.unlock();
}
}
//有超时时间的生产者
public void prdTime(T t,long time,TimeUnit unit){
//得到当前长度
lock.lock();
try{
int size = size();
time = unit.toNanos(time);
//添加超时时间并且结束程序
long beginTime = System.nanoTime();
long passTime = 0;
//当前阻塞队列已经存放满了,此时生产者的condition进入await
while(size() == capity){
try {
long waitTime = time - passTime;
if(waitTime < 0)
prdCondition.await(waitTime,unit);
passTime = System.nanoTime() - beginTime;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//阻塞队列数量小于容量时,将信息放入至阻塞队列中
deque.addLast(t);
cusCondition.signalAll();
}finally {
lock.unlock();
}
}
public T cusTime(Integer time){
lock.lock();
try{
int size = size();
long beginTime = System.currentTimeMillis();
long passTime = 0;
while (size() == 0){
log.debug("阻塞队列没有内容");
long waitTime = time - passTime;
if(waitTime < 0){
try {
cusCondition.await(waitTime,TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
passTime = System.currentTimeMillis() - beginTime;
}
prdCondition.signalAll();
return deque.removeFirst();
}finally {
lock.unlock();
}
}
public int size(){
lock.lock();
try{
return deque.size();
}finally {
lock.unlock();
}
}
}