多线程的引入,可以大大增强系统的并发能力,但是创建一个线程的开销是很大的,频繁的创建和销毁线程反而使得我们的系统在高并发时性能急剧下降。如果线程用完了,先不着急销毁,有下个任务来了,再重复利用,是不是就好多了。线程池就是这样做的,当一个新任务到来的时候,线程池会找到一个空闲的线程来执行任务。如果线程池里的线程都用完了,那么线程池会将任务加入到一个队列中去等待。当队列也满了,线程池会根据要求创建一个新的线程来执行任务,或者执行某种拒绝策略。
为了让我们的线程池不那么复杂,我们暂时去掉一些高级的功能:
1、线程池满了,任务加入等待队列。
2、队列满,创建新线程。
3、拒绝策略。
瘦身之后,我们的线程池要实现的功能就包括:
1、线程池,拥有一定数量线程。
2、新任务到来,从线程池中获取空闲线程执行任务。
3、线程池满,则无限等待,直到有空闲线程为止。
4、其他功能:初始化设置,常用方法、关闭线程池。
这样我们的线程池接口的雏形就出来了:
/**
* 线程池
* Created by gameloft9 on 2019/4/24.
*/
public interface ThreadPool {
int getPoolSize();
void init();
void shutdown();
void execute(Runnable runnable);
}
这是一个简约但不简单的线程池,它提供了初始化和关闭线程池的方法,还额外提供了一个获取线程池大小的方法,当然最核心的是提供了执行任务的方法。
线程池应该包括那些属性呢?首先肯定有一个count属性来保存线程池大小,其次我们有一个存放线程的列表,为了方便操作我们分成了三个列表,总线程列表,空闲线程列表和繁忙线程列表。对线程列表的操作属于临界资源,所以还需要一个锁对象,还有一些别的属性这里就不一一列举了,最后我们的线程池实现类大致就是下面这个样子:
/**
* 线程池实现
* Created by gameloft9 on 2019/4/24.
*/
public class MyThreadPool implements ThreadPool {
private Logger log = LoggerFactory.getLogger(getClass());
// 线程名称前缀
private static final String THREAD_NAME_PREFIX = "my_thread_";
// 线程池线程个数
private int count = -1;
// 线程优先级
private int prior = Thread.NORM_PRIORITY;
// 是否守护线程
private boolean daemon = false;
// 临界资源锁
private final Object poolLock = new Object();
// 是否线程池关闭
private boolean shutdown = false;
// 是否初始化完成(避免还未初始化完成就调用execute方法)
private boolean initialized = false;
private HashSet<WorkerThread> workers = new HashSet<WorkerThread>();
private LinkedList<WorkerThread> idleWorkers = new LinkedList<WorkerThread>();
private LinkedList<WorkerThread> busyWorkers = new LinkedList<WorkerThread>();
public MyThreadPool() {
}
public MyThreadPool(int count, int prior) {
this.count = count;
this.prior = prior;
}
public int getPoolSize() {
return count;
}
public void init() {
}
public void shutdown() {
}
public void execute(Runnable runnable) {
}
}
线程池的初始化、执行任务、关闭都要涉及到具体的线程,我们称其为工作线程。我们的工作线程并不是简单的线程,首先它需要知道何时有任务加入,其次任务完成后它需要告诉线程池任务已经结束,自己可以继续接受新的任务。最后当线程池关闭时,本线程也需要退出。
下面是据此写出的工作线程类,新任务通过run(Runnable)方法为工作线程赋值,然后通过notifyAll()通知其有新任务加入。工作线程循环判断runnable是否为空,如果不为空则运行,否则wait一下释放掉锁。任务完成后,将runnable置为null,并通过线程池将自己从busyWorkers列表中去掉,并放入idleWorkers列表。关闭时,将工作线程状态置为关闭,这样run方法就会退出,线程就结束了。
class WorkerThread extends Thread {
// 锁,用于加入任务
private final Object runLock = new Object();
// 待跑的任务
private Runnable runnable = null;
// 指示线程是否shutdown
private AtomicBoolean run = new AtomicBoolean(true);
// 线程池
MyThreadPool tp;
public WorkerThread(MyThreadPool tp, ThreadGroup group, String name, int prior, boolean daemon) {
super(group, name);
this.tp = tp;
setPriority(prior);
setDaemon(daemon);
}
public void shutDown() {
this.run.set(false);
}
public void run(Runnable newRunnable) {
synchronized (runLock) {
if (null != runnable) {
throw new IllegalStateException("Already running a Runnable!");
}
if (!run.get()) {// 线程已经关闭,无法接受任务
return;
}
this.runnable = newRunnable;
runLock.notifyAll();
}
}
public void run() {
while (run.get()) {
try {
synchronized (runLock) {
if (runnable == null && run.get()) {
runLock.wait(500);
}
if (null != runnable) {
runnable.run();
}
}
} catch (InterruptedException e) {
log.error("线程执行异常", e);
} finally {
synchronized (runLock) {
runnable = null;
}
tp.makeIdle(this);
}
}
}
}
我们再回到我们的线程池,完成剩下的几个方法。
线程池初始化
初始的工作很简单,就是创建线程,并将其加入到总线程列表和空闲线程列表中去。
public void init() {
synchronized (poolLock){
if (!initialized) {
Iterator<WorkerThread> it = createWorkers(count, prior).iterator();
while (it.hasNext()) {
WorkerThread worker = it.next();
worker.start();
if(!idleWorkers.contains(worker)){
idleWorkers.add(worker);
}
}
initialized = true;
}
poolLock.notifyAll();
}
}
执行任务
执行任务,重点是判断是否有空闲线程列表,如果没有则wait一下,释放掉锁。如果有则从空闲列表中取出第一个,将其加入到繁忙线程列表中,然后调用run()方法添加任务。
public void execute(Runnable runnable) {
if (null == runnable) {
return;
}
synchronized (poolLock) {
while ((idleWorkers.size() < 1) && initialized && !shutdown) {
try {
log.info("暂无可用线程,等待500ms...");
poolLock.wait(500);
} catch (InterruptedException e) {
log.error("异常",e);
}
}
if (initialized && !shutdown) {
WorkerThread worker = idleWorkers.removeFirst();
busyWorkers.add(worker);
worker.run(runnable);
}
poolLock.notifyAll();
}
}
关闭线程池
关闭线程池,遍历线程列表,并调用shutdown方法。然后将其从空闲线程列表和繁忙线程列表中去掉。
public void shutdown() {
synchronized (poolLock) {
shutdown = true;
if (workers == null){
return;
}
Iterator<WorkerThread> it = workers.iterator();
while (it.hasNext()) {
WorkerThread worker = it.next();
worker.shutDown();
idleWorkers.remove(worker);
busyWorkers.remove(worker);
}
poolLock.notifyAll();
}
}
好了,到目前为止我们的线程池就完成了。我们来测试一下,我们创建一个拥有3个线程的线程池,优先级为5。然后我们循环加入10个任务,每个任务都sleep 1秒来模拟任务执行。然后我们新建一个线程,模拟2秒后关闭线程池。
/**
* 线程池测试类
* Created by gameloft9 on 2019/4/24.
*/
@Slf4j
public class TestThreadPool {
public static void main(String[] args) throws Exception{
final ThreadPool tp = new MyThreadPool(3,5);
log.info("线程池大小:{}",tp.getPoolSize());
tp.init();
// 给一点时间让其初始化完成,这一个不要也可以,只是为了让结果更直观
Thread.sleep(2000);
// 2s后我们关闭线程池
Thread term = new Thread(new Runnable() {
public void run() {
log.info("关闭线程池");
try{
Thread.sleep(2000);
}catch(InterruptedException e){
log.error("",e);
}
tp.shutdown();
}
});
term.start();
// 循环添加10个任务
for(int i = 1;i<=10;i++){
final int index = i;
tp.execute(new Runnable() {
public void run() {
try{
log.info("任务_{}开始执行..",index);
Thread.sleep(1000);
log.info("任务_{}执行成功。",index);
}catch(Exception e){
log.error("异常",e);
}
}
});
}
}
}
下面是执行结果:
从结果中我们可以看到,执行到任务6的时候,因为线程池关闭了,所以后续的任务就没有执行了。
其实上述的线程池核心设计就是Quartz的SimpleThread的设计,只是我们稍微有一些不同而已。感兴趣的同学可以翻看一下Quartz-2.2.1的源码。