自定义线程池

Java自定义线程池

线程池概念图

线程池的组成

简单聊一聊线程池:线程的多次重复创建需要消耗系统大量的资源,而线程池的做法是当需要大量的线程时,由线程池统一创建,交由main线程使用,且可重复使用,避免了创建大量的线程。

这里是一个最简单的线程池,由三部分组成:线程池,阻塞队列,main调用者。

线程池:用来创建线程,创建的线程用worker表示,用workers集合存储

参数:

任务队列:queue

线程集合:workers

核心数:coreSize(用来限制创建的worker数量,即只允许线程池创建多少个线程)

阻塞队列:用于两种情况:

1)当main调用者传来很多个任务(task)时,er线程池的worker不够用,这个时候这些task进入阻塞队列,开始等待,为了区分,这里将这种情况命名为Task阻塞

2)当线程池的worker有空闲时,所有main调用者的task全部被执行完毕,这时候还没有新的taskchuanlai,而worker却是多了的一方,这时候空闲的worker进入阻塞队列,同样为了区分,将这种情况命名为Worker阻塞

因此阻塞队列里至少有两种方法:Task阻塞,Worker阻塞

参数:

队列:queue

锁:lock(ReentrantLock)

生产者条件变量:fullWaitSet,当Task阻塞情况发生,而且阻塞队列满了的时候,应该进行等待,不能继续先阻塞队列添加task

消费者条件变量:emptyWaitSet,当Worker阻塞情况发生,即阻塞队列为空,且没有新的task传来时,应该让worker进行等待

容量上限:capcity,阻塞队列的容量上限

创建工程

接下来用代码进行演示

首先创造一个空的maven工程

引入两个jar

<dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>

阻塞队列

创建一个类 TestPool(一个类就够了)

编写阻塞队列代码段(注意这是一个类,不是内部类),参数如下:

@Slf4j
class BlockQueue {

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

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

        //生产者条件变量,等待的数量达到一定数目时,开始生产
        private Condition fullWaitSet = lock.newCondition();

        //消费者条件变量,
        private Condition emptyWaitSet = lock.newCondition();

        //容量上限
        private int capcity;

        public BlockQueue(int capcity) {
            this.capcity = capcity;
        }

        
}

接下编写阻塞队列的两个方法,即Task阻塞和Worker阻塞

Task阻塞方法,命名为take:

public T take(){
        lock.lock();
        try {
            while (queue.isEmpty()){
                try {
                    log.debug("队列为空");
                    //队列是否为空
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //移除
            T t = (T) queue.removeFirst();
            //唤醒生产者
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

解释一波:首先加锁,这个时候有个try代码块用来实现具体操作,还有一个finally代码块释放锁

然后再try代码块中:使用while循环判断阻塞队列是否为空,如果为空,日志打印,然后将emptyWaitSet标志为等待状态,因为这个时候阻塞队列里面没有task任务。

如果不为空,即代表阻塞队列里面有task任务,则移除队列里面的第一个,并且唤醒fullWaitSet。因为此刻移除了一个阻塞队列中的task,导致阻塞队列并不是满的,因此唤醒fullWaitSet可恶意继续向阻塞队列中添加task

Worker阻塞方法,命名为put:

 public void put(T task){
        lock.lock();
        try {
            //队列是否已经满了
            while (queue.size()==capcity){
                try {
                    log.debug("等待加入任务队列{}...",task);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //添加
            log.debug("加入任务队列{}",task);
            queue.addLast(task);
            //唤醒消费者
            emptyWaitSet.signal();
        }finally {
            lock.unlock();
        }
    }

解释一波:首先加锁,这个时候有个try代码块用来实现具体操作,还有一个finally代码块释放锁

然后再try代码块中:判断阻塞队列里面的数量是否等于设置的容量上限,如果是,日志打印,然后将fullWaitSet标志为等待状态,因为这个时候阻塞队列已经满了,全是task任务,需要停下来,不能继续往阻塞队列里面添加task

如果不等于设置的容量上限,即代表还可以继续往阻塞队列里面添加task任务,则往里面添加,且代表阻塞队列至少有一个task(刚刚添加的),因此唤醒emptyWaitSet,让take方法继续执行

还有一个获取队列里面task数量的方法:

 //获取大小
    public int size(){
        lock.lock();
        try {
            return queue.size();
        }finally {
            lock.unlock();
        }
    }

线程池

接下来编写线程池类,参数如下:

@Slf4j
class ThreadPool{
    //任务队列
    private BlockQueue<Runnable> taskQueue;

    //线程集合
    private HashSet<Worker> workers=new HashSet();

    //核心线程数
    private int coreSize;

    //获取任务的超时时间
    private long timeout;
    private TimeUnit unit;
    
}

构造方法:

public ThreadPool(int coreSize, long timeout, TimeUnit unit,int queueCapcity) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.unit = unit;
        this.taskQueue=new BlockQueue<>(queueCapcity);
    }

编写执行task任务的方法execute:

 public void execute(Runnable task){
        //当任务数没有超过coreSize时,直接交给worker对象执行
        //如果任务数超过coreSize时,加入任务队列暂存起来
        synchronized (workers){
            if (workers.size()<coreSize){
                Worker worker = new Worker(task);
                log.debug("新增worker{},{}",worker,task);
                workers.add(worker);
                worker.start();
            }else {

                taskQueue.put(task);

            }
        }
    }

解释一波:首先传过来的参数是一个Runnable的task,就是一个任务

然后使用synchronized 锁对workers集合进行加锁,判断已有的worker数量(线程池已经创建的线程数量)是否小于设置的核心数,如果小于,则继续创建新的worker,并且加入workers集合中,并用这个新的worker来完成这个task任务

如果达到设置的线程数了,即代表没有空闲的worker了,则使用阻塞队列的对象(第一个引进来的对象)的put方法,将这个task任务放进阻塞队列暂存起来,后面步骤参考上面的解释

编写一个内部类Worker,继承Thread类

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){

                }finally {
                    task=null;
                }
            }
            synchronized (workers){
                log.debug("worker被移除{}",this);
                workers.remove(this);
            }
        }
    }

解释一波:重写runff,首先判断传过来的task是不是null或者阻塞队列里面还有没task,只要有,则执行run方法,并且执行完之后进这个task设为null,然后继续while循环,知道那两个条件都不能满足

如果都不满足则代表阻塞队列里面没有task任务且没有新的task传来,这时候循环结束,执行另外一个方法,将这个worker线程在workers集合中移除掉,加锁是为了防止在移除worker时,还有其他线程对workers集合进行操作

测试

线程池和阻塞队列都已经写好,接来进行测试,测试代码:

@Slf4j
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS,2);
        for (int i = 0; i < 4; i++) {
            int j=i;
            threadPool.execute(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("{}",j);

            });
        }
    }
}

解释一波:这里创建的线程池参数为:1个核心数,1000毫秒的等待(目前还没有),阻塞队列的大小2。意思就是阻塞队列里面只能塞两个task,线程池只能创建1个线程

另外main调用者(主函数)这里想要创建4个任务,而且每个任务要执行1秒钟(太快了没感觉),另外用j表示哪一个任务并用日志打印出来

控制台输出日志:

23:29:56.659 [main] DEBUG com.lzx.test.ThreadPool - 新增workerThread[Thread-0,5,main],com.lzx.test.TestPool$$Lambda$1/1869997857@1e81f4dc
23:29:56.662 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@9e89d68
23:29:56.662 [main] DEBUG com.lzx.test.BlockQueue - 等待加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32...
23:29:56.662 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@1e81f4dc
23:29:57.662 [Thread-0] DEBUG com.lzx.test.TestPool - 0
23:29:57.662 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@9e89d68
23:29:57.662 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32
23:29:57.662 [main] DEBUG com.lzx.test.BlockQueue - 等待加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d...
23:29:58.662 [Thread-0] DEBUG com.lzx.test.TestPool - 1
23:29:58.662 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32
23:29:58.662 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d
23:29:59.663 [Thread-0] DEBUG com.lzx.test.TestPool - 2
23:29:59.663 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d
23:30:00.663 [Thread-0] DEBUG com.lzx.test.TestPool - 3
23:30:00.663 [Thread-0] DEBUG com.lzx.test.BlockQueue - 队列为空

一行一行解释吧:

1.首先第一个任务进去,这时候线程池里面还没有创建线程,因此新创建了一个线程并执行这个任务

2.然后第二个任务进来,因为只有一个worker,所以没办法,只能进入阻塞队列暂存起来

3..第三个任务也进来了,这时候worker没空,阻塞队列又满了,因为设置阻塞队列的个数设置为2,而第一个和第二个正在里面,因此这时候将fullWaitSet设置为等待状态,不在往阻塞队列里面添加task

4.这是第一个任务正在执行,但是还没结束,快了

5.第一个任务执行完毕,并将这task移除掉,并且唤醒fullWaitSet

6.这时候立刻开始执行第二个任务

7.因为fullWaitSet被唤醒,所以第三个任务成功接入到阻塞队列中

8.第四个任务也想进来,但是没办法,阻塞队列满了,又将fullWaitSet设置为等待状态,不在往阻塞队列里面添加task

9.第二个任务执行完毕,并将这task移除掉,并且唤醒fullWaitSet

10.这时候立刻开始执行第三个任务

11.因为fullWaitSet被唤醒,所以第四个任务成功接入到阻塞队列中

12.第三个任务执行完毕,并将这task移除掉,并且唤醒fullWaitSet(尽管fullWaitSet并没有沉睡)

13.这时候立刻开始执行第四个任务

14.第四个任务执行完毕,并将这task移除掉,并且唤醒fullWaitSet(尽管fullWaitSet并没有沉睡)

15.因为阻塞队列里面的task为空且没有新的task传来,所以打印日志“队列为空”,且将emptyWaitSet设置为等待状态

代码优化

这里并没有设置超时等待,因此程序并没有结束,而是一直在等待

接下来改进代码

首先修改BlockQueue里面的两个阻塞方法:

Task阻塞

  public T poll(long timeout, TimeUnit unit){
        lock.lock();
        try {
            //将其他时间(timeout)统一转换为纳秒
            long nanos = unit.toNanos(timeout);
            //队列是否为空
            while (queue.isEmpty()){
                log.debug("超时队列为空");
                try {
                    //剩余时间小于等于0,无需等待,直接返回
                   if (nanos<=0){
                       return null;
                   }
                   //如果等待途中被唤醒,则返回一个还需要等待的时间,即总时间-被唤醒时等待了的时间=新的时间(nanos)
                    nanos=emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //移除
            T t = (T) queue.removeFirst();
            //唤醒生产者
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

解释一波:大致没变,加入了时间概念。

首先判断超时时间是否小于0,小于0就直接return。 

关键点在于这个地方:nanos=emptyWaitSet.awaitNanos(nanos); 因为有可能中途被唤醒,等待时间没到,不过这里并不会被唤醒,因为返回的nanos是一个差值,不到时间它会继续沉睡

Worker阻塞

public boolean offer(T task,long timeout,TimeUnit unit){
        lock.lock();
        try {
            long nanos = unit.toNanos(timeout);
            //队列是否已经满了
            while (queue.size()==capcity){
                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();
        }
    }

没啥改变,和上面一样理解

设置等待一段时间后,程序停止,修改内部类里面的run方法:

  @Override
        public void run() {
            //执行任务
            //1)当task不为空,执行任务
            //2)当task执行完毕,再从任务队列获取任务并执行
            while (task!=null || (task=taskQueue.poll(timeout,unit)) !=null){
                try {
                    log.debug("正在执行...{}",task);
                    task.run();
                }catch (Exception e){

                }finally {
                    task=null;
                }
            }
            synchronized (workers){
                log.debug("worker被移除{}",this);
                workers.remove(this);
            }
        }

不修改main里面的参数,运行代码,控制台得到的日志:

23:51:28.418 [main] DEBUG com.lzx.test.ThreadPool - 新增workerThread[Thread-0,5,main],com.lzx.test.TestPool$$Lambda$1/1869997857@1e81f4dc
23:51:28.421 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@9e89d68
23:51:28.421 [main] DEBUG com.lzx.test.BlockQueue - 等待加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32...
23:51:28.421 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@1e81f4dc
23:51:29.421 [Thread-0] DEBUG com.lzx.test.TestPool - 0
23:51:29.421 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@9e89d68
23:51:29.421 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32
23:51:29.421 [main] DEBUG com.lzx.test.BlockQueue - 等待加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d...
23:51:30.422 [Thread-0] DEBUG com.lzx.test.TestPool - 1
23:51:30.422 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@3b192d32
23:51:30.422 [main] DEBUG com.lzx.test.BlockQueue - 加入任务队列com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d
23:51:31.422 [Thread-0] DEBUG com.lzx.test.TestPool - 2
23:51:31.422 [Thread-0] DEBUG com.lzx.test.ThreadPool - 正在执行...com.lzx.test.TestPool$$Lambda$1/1869997857@311d617d
23:51:32.422 [Thread-0] DEBUG com.lzx.test.TestPool - 3
23:51:32.422 [Thread-0] DEBUG com.lzx.test.BlockQueue - 超时队列为空
23:51:33.423 [Thread-0] DEBUG com.lzx.test.BlockQueue - 超时队列为空
23:51:33.423 [Thread-0] DEBUG com.lzx.test.ThreadPool - worker被移除Thread[Thread-0,5,main]

Process finished with exit code 0

这里就不再解释了,可以看到的是代码在等待了一段时间之后自己停止了

拒绝策略

再进行优化,加入拒绝策略

在之前的代码中,当面对没有task任务时,第一种处理方式是死等,线程就一直运行;第二种处理方式是等待一段时间,然后自己结束。

两种不同的方式需要不同的代码,因此并不采纳,这里采用拒绝策略,让我们在不修改核心代码的前提下自己修改处理方式。

首先在类里面添加一个接口

@FunctionalInterface//拒绝策略
interface RejectPolicy<T>{
    void reject(BlockQueue queue,T task);
        }

线程池里面添加一个参数,修改如下:

@Slf4j
class ThreadPool{
    //任务队列
    private BlockQueue<Runnable> taskQueue;

    //线程集合
    private HashSet<Worker> workers=new HashSet();

    //核心线程数
    private int coreSize;

    //获取任务的超时时间
    private long timeout;
    private TimeUnit unit;

    private RejectPolicy<Runnable> rejectPolicy;

    //执行任务
    public void execute(Runnable task){
        //当任务数没有超过coreSize时,直接交给worker对象执行
        //如果任务数超过coreSize时,加入任务队列暂存起来
        synchronized (workers){
            if (workers.size()<coreSize){
                Worker worker = new Worker(task);
                log.debug("新增worker{},{}",worker,task);
                workers.add(worker);
                worker.start();
            }else {
                //此方法位死等,用新方法,可自己管理策略
//                taskQueue.put(task);              
                //让调用者自己执行
                taskQueue.tryPut(rejectPolicy,task);
            }
        }
    }

    public ThreadPool(int coreSize, long timeout, TimeUnit unit,int queueCapcity,RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.unit = unit;
        this.taskQueue=new BlockQueue<>(queueCapcity);
        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,unit)) !=null){
                try {
                    log.debug("正在执行...{}",task);
                    task.run();
                }catch (Exception e){

                }finally {
                    task=null;
                }
            }
            synchronized (workers){
                log.debug("worker被移除{}",this);
                workers.remove(this);
            }
        }
    }
}

在阻塞队列BlockQueue类中添加方法:

public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            //判断队列是否满
            if (queue.size()==capcity){
                rejectPolicy.reject(this,task);
            }else {
                //有空闲
                //添加
                log.debug("加入任务队列{}",task);
                queue.addLast(task);
                //唤醒消费者
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }

main方法中:

@Slf4j
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1,(queue,task)->{
            //死等
            queue.put(task);
            //2)带超时等待
//            queue.offer(task,1500,TimeUnit.MILLISECONDS);
            //3)让调用者放弃任务执行
//            log.debug("放弃{}",task);
            //4)让调用者抛出异常
//            throw new RuntimeException("任务执行失败 "+task);
            //5)让调用者自己执行
//            task.run();
        });
        for (int i = 0; i < 4; i++) {
            int j=i;
            threadPool.execute(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("{}",j);

            });
        }
    }
}

列举了5种策略,可自行选择

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值