手写简单线程池

由于业务接触到了多线程,处理GPS数据,包括通过GPS去计算作业时长这些业务,看了大佬留下的代码晦涩难懂,于是先从简单的用起来,由于业务复杂,大佬采用多线程和EventBus等等 ,本文只先说多线程

前提知识

Deque

Deque (发音为"deck") 是双端队列的缩写,它是一种允许在两端(头部和尾部)进行插入和删除操作的线性数据结构。

  • 双端访问: 这是 deque 最重要的特征。你可以从 deque 的两端高效地添加或移除元素。
  • 高效的插入和删除: 在 deque 的两端进行插入和删除操作通常具有 O(1) 的时间复杂度,这使得它非常适合需要频繁在两端进行操作的应用场景。
  • 动态大小: 大多数 deque 实现都能够动态调整大小,以适应不断变化的数据量。
  • 非线性访问: 虽然可以在两端快速访问,但访问 deque 中间位置的元素则需要线性时间,效率较低。

ReentrantLock

lock

Lock 的接口定义

java 复制代码
public interface Lock {

  
    void lock();

    void lockInterruptibly() throws InterruptedException;

    
    boolean tryLock();

    
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

 
    void unlock();

   
    Condition newCondition();
}

通过这些接口函数的定义,我们可以发现 Lock具备一些灵活性,尤其是在它的以下几个特性方面:

  • 支持获取锁超时机制;

  • 支持非阻塞方式获取锁;

  • 支持可中断方式获取锁。

在JDK包中,Lock 实现类的代表莫过于是 ReentrantLock

Condition

Condition 是一种高级别的线程协调工具,它能够让线程在特定的条件下等待或通知其他线程。Condition 可以与 Lock 一起使用,来实现更复杂的线程协调。

以下是 Condition 的常用方法:

  • await():当前线程进入等待状态,直到其他线程调用 signal()signalAll() 方法才会被唤醒。
  • signal():唤醒一个正在等待的线程。
  • signalAll():唤醒所有正在等待的线程。
ReentrantLock

ReentrantLock是一种基于AQS(Abstract Queued Synchronizer)框架的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

思路

采用经典的设计模型------生产者消费者模型

首先得有得有一个任务等待队列,把任务都丢进去

然后生产者放入任务

消费者消费任务

由于我是自己写一个简易的,就有main负责put任务到任务等待队列

由线程池去task任务

任务等待队列BlockingQueue

java 复制代码
@Slf4j(topic = "c.BlockingQueue")
public class BlockingQueue<T> {
    // 容量
    private int capcity;
    // 双端任务队列容器
    private Deque<T> deque = new ArrayDeque<>();
    // 重入锁
    private ReentrantLock lock = new ReentrantLock();
    // 生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    // 生产者条件变量
    private Condition emptyWaitSet = lock.newCondition();

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

    // 阻塞的方式添加任务
    public void put(T task) {
        lock.lock();
        try {
            // 通过while的方式
            while (deque.size() >= capcity) {
                log.debug("wait to add queue");
                try {
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            deque.offer(task);
            log.debug("task add successfully");
            emptyWaitSet.signal();
        }  finally {
            lock.unlock();
        }
    }

    // 阻塞获取任务
    public T take() {
        lock.lock();
        try {
            // 通过while的方式
            while (deque.isEmpty()) {
                try {
                    log.debug("wait to take task");
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            fullWaitSet.signal();
            T task = deque.poll();
            log.debug("take task successfully");
            // 从队列中获取元素
            return task;
        } finally {
            lock.unlock();
        }
    }
}

线程池消费端实现

  1. 定义执行器接口
php 复制代码
php 代码解读复制代码/**
 * <p>定义一个执行器的接口:</p>
 *
 * @author: cxw (332059317@qq.com)
 * @date: 2022/10/18  12:31
 * @version: 1.0.0
 */
public interface Executor {

    /**
     * 提交任务执行
     * @param task 任务
     */
    void execute(Runnable task);
}
  1. 定义线程池类实现该接口
java 复制代码
java 代码解读复制代码@Slf4j(topic = "c.ThreadPool")
public class ThreadPool implements Executor {

    /**
     * 任务队列
     */
    private BlockingQueue<Runnable> taskQueue;

    /**
     * 核心工作线程数
     */
    private int coreSize;

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

    /**
     *  创建线程池
     * @param coreSize 工作线程数量
     * @param capcity 阻塞队列容量
     */
    public ThreadPool(int coreSize, int capcity) {
        this.coreSize = coreSize;
        this.taskQueue = new BlockingQueue<>(capcity);
    }

    /**
     * 提交任务执行
     */
    @Override
    public void execute(Runnable task) {
        synchronized (workers) {
            // 如果工作线程数小于阈值,直接开始任务执行
            if(workers.size() < coreSize) {
                Worker worker = new Worker(task);
                workers.add(worker);
                worker.start();
            } else {
                // 如果超过了阈值,加入到队列中
                taskQueue.put(task);
            }
        }
    }

    /**
     * 工作线程,对执行的任务做了一层包装处理
     */
    class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            // 如果任务不为空,或者可以从队列中获取任务
            while (task != null || (task = taskQueue.take()) != null) {
                try {
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 执行完后,设置任务为空
                    task = null;
                }
            }

              // 移除工作线程
            synchronized (workers){
                log.debug("remove worker successfully");
                workers.remove(this);
            }
        }
    }
}

演示

java 复制代码
@Slf4j(topic = "c.ThreadPool")
public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Hello world!");
        Executor executor = new ThreadPool(2, 4);
        // 提交任务
        for (int i = 0; i < 6; i++) {
            final  int j = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(10);
                    log.info("run task {}", j);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread.sleep(10);
        }

        Thread.sleep(10000);

    }
}

由于只是简单demo,没有考虑超过丢弃等等问题,只是作为入门

相关推荐
m0_74825718几秒前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
小_太_阳1 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
搬码后生仔2 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱2 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3523 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
贵州晓智信息科技3 小时前
如何优化求职简历从模板选择到面试准备
面试·职场和发展
小池先生3 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟4 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
小蜗牛慢慢爬行4 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate