并发编程[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]