并发编程[9]_自定义线程池


使用线程池可以减少创建和销毁线程的次数,可以调整线程池中工作线程的数量,防止内存过多消耗。
在阿里巴巴Java开发手册中,也强调了线程资源必须通过线程池提供,不允许在应用中自行显式创建线程,应通过ThreadPoolExecutor的方式来创建线程池,规避资源耗尽的风险。
在学习Java的线程池之前,我们先来手动写一个线程池,来熟悉线程池的类似实现。

1. 线程任务队列类

我们先创建一个线程任务队列类,用于存放线程池即将处理的任务。此类是线程安全类。

  • 任务存放在任务队列
  • 规定容量capacity,用于判断队列容量是否已满
  • 往任务队列取元素时,若队列为空,就会阻塞等待,否则取队头任务,唤醒消费者条件等待的任务
  • 往任务队列加元素时,若队列为满时,就会阻塞等待,否则加到队尾,唤醒生产者条件等待的任务
class BlockingQueue<T> {
    //    1. 任务队列
    private Deque<T> deque = 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 capacity) {
        this.capacity = capacity;
    }

    //    带超时的阻塞获取
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            long nanos = unit.toNanos(timeout);
            while (deque.isEmpty()) {
                try {
                    if (nanos <= 0) {
                        return null;
                    }
                    // 返回的是剩余时间
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    //    阻塞获取
    public T take() {
        lock.lock();
        try {
            while (deque.isEmpty()) {
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }

    }

    //    阻塞添加
    public void put(T element) {
        lock.lock();
        try {
            while (capacity == deque.size()) {
                try {
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            deque.addLast(element);
            emptyWaitSet.signal();
        } finally {
            lock.unlock();
        }
    }

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

2. 自定义线程池类

  • 创建自定义线程池类

我们创建自定义线程池类,属性有任务队列,线程集合,线程数,超时时间,超时时间单位,有构造函数来初始化线程池的基本属性信息。
线程数:线程池最多可以同时创建的线程数量,创建后的线程存放入线程集合中,超过这个数量的任务,得进入任务队列进行等待。
超时时间:超过一定时间没有新任务就会销毁掉线程。

    /*
     * 自定义线程池
     * */
    static class ThreadPool {
        //  任务队列
        private BlockingQueue<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 queueCapacity) {
            this.coreSize = coreSize;
            this.timeout = timeout;
            this.unit = unit;
            this.taskQueue = new BlockingQueue<>(queueCapacity);
        }

        public void execute(Runnable task) {
            ......
        }

        class Worker extends Thread {
            ......
        }
    }
  • execute() 详细内容

线程池执行任务。
传入Runnable对象,如果线程集合大小没有超过最大线程数,就可以将Runnable对象交给Work类,Work类继承于Thread类。work线程启动后,线程集合就会添加启动的work对象。
如果线程集合大小大于或等于最大线程数,便不能再创建新线程,而应该将当前任务放入任务队列中。

        public void execute(Runnable task) {
            synchronized (workers) {
                // 任务数没有超过线程数,就直接交给work,超过的话,加入任务队列暂存
                if (workers.size() < coreSize) {
                    Worker worker = new Worker(task);
                    workers.add(worker);
                    log.debug("启动worker{}{}", worker,task);
                    worker.start();
                } else {
                    log.debug("等待任务{}", task);
                    taskQueue.put(task);
                }
            }
        }
  • Worker类

Worker类继承于Thread类。
run方法中的主要逻辑是,循环判断所需要执行的task任务对象是否为空,不为空的话,可以直接执行任务对象的run方法,若为空的话,所需要执行的任务便是从任务队列取出来的任务对象。
若没有需要执行的任务或已过超时时间限制(从任务队列取值的超时限制已过),便销毁自身的线程(从线程集合中移除)。

        class Worker extends Thread {
            private Runnable task;

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

            public void run() {
                // task 不为空,执行任务
                // task为空,从任务队列拿出来执行
                while (task != null || (task = taskQueue.poll(timeout,unit)) != null) {
                    try {
                        log.debug("执行{}",task);
                        task.run();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        task = null;
                    }
                }

                synchronized (workers) {
                    workers.remove(this);
                    log.debug("移除{}", this);
                }
            }

        }

3. 测试

新建线程池对象,线程数为2,超时时间为1秒,等待队列的容量为5。
新建五个任务,那么线程池中就应该只会有两个线程池在工作,其他的任务就会在任务队列中,等待其他的任务执行完毕后才会执行。

    public static void main(String[] args) {
        ThreadPool pool = new ThreadPool(2, 1, TimeUnit.SECONDS, 5);
        for (int i = 0; i < 5; i++) {
            int j = i;
            pool.execute(()->{
                log.debug("输出{}",j);
            });
        }
    }

运行结果:

2021-05-04 13:43:41:699 [main] - 启动workerThread[Thread-0,5,main]com.yt.concurrent.MyPool$$Lambda$1/1604839423@3fa77460
2021-05-04 13:43:41:701 [main] - 启动workerThread[Thread-1,5,main]com.yt.concurrent.MyPool$$Lambda$1/1604839423@7ab2bfe1
2021-05-04 13:43:41:702 [main] - 等待任务com.yt.concurrent.MyPool$$Lambda$1/1604839423@497470ed
2021-05-04 13:43:41:703 [main] - 等待任务com.yt.concurrent.MyPool$$Lambda$1/1604839423@63c12fb0
2021-05-04 13:43:41:704 [main] - 等待任务com.yt.concurrent.MyPool$$Lambda$1/1604839423@b1a58a3
2021-05-04 13:43:41:705 [Thread-0] - 执行com.yt.concurrent.MyPool$$Lambda$1/1604839423@3fa77460
2021-05-04 13:43:41:705 [Thread-1] - 执行com.yt.concurrent.MyPool$$Lambda$1/1604839423@7ab2bfe1
2021-05-04 13:43:41:705 [Thread-0] - 输出0
2021-05-04 13:43:41:705 [Thread-1] - 输出1
2021-05-04 13:43:41:706 [Thread-1] - 执行com.yt.concurrent.MyPool$$Lambda$1/1604839423@63c12fb0
2021-05-04 13:43:41:706 [Thread-0] - 执行com.yt.concurrent.MyPool$$Lambda$1/1604839423@497470ed
2021-05-04 13:43:41:706 [Thread-1] - 输出3
2021-05-04 13:43:41:706 [Thread-0] - 输出2
2021-05-04 13:43:41:706 [Thread-1] - 执行com.yt.concurrent.MyPool$$Lambda$1/1604839423@b1a58a3
2021-05-04 13:43:41:708 [Thread-1] - 输出4
2021-05-04 13:43:42:717 [Thread-0] - 移除Thread[Thread-0,5,main]
2021-05-04 13:43:42:718 [Thread-1] - 移除Thread[Thread-1,5,main]
posted @ 2024-08-23 10:53  Aeons  阅读(7)  评论(0编辑  收藏  举报