本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接
个人唠叨
我把一篇文章劈成7篇,幸运的是,它们都活了下来,并参加了掘金的Java 笔记debug活动。听闻如果再次把它合到一起,可能会变成一个switch。但我发现它们合在一起成功率只有0.0001。
前言
网上的对线程池源码的分析太多了,这里就不说原理了。直接参考它写一个简单的线程池吧。我记得google 的volley 写的更简单暴力,就直接for循环出来几个线程,然后阻塞读取任务。
开始循序渐进....记得点赞额
(⊙o⊙)…
强烈建议先看前面这两文章
敲重点
Java运算符扫盲
JAVA线程池源码之深入状态值分析
先画个流程图
我们按照流程图,编写代码。
核心线程处理类
我们先定义几个变量。ThreadPoolExecutor中是用AtomicInteger,代表线程状态和在工作线程数 ,目的是为了减少锁的竞争。简单起见就不了。 待我再写一篇文章吧!这个状态有点意思。
/**
* 最大任务数量,超过就不进队列。
*/
private final int maximumPoolSize;
/**
*核心线程数,默认值3
*/
private volatile int threadSize = 3;
/**
* 任务阻塞队列
*/
private final BlockingQueue<Runnable> workQueue;
/**
* 记录目前运行的线程数
*/
private final AtomicInteger threadCount = new AtomicInteger(0);
处理任务
这里分三步走:
新建线程
startThread(count);
如果目前在运行线程,没有达到最大,就继续新建线程
添加任务
workQueue.add(task);
如果任务队列未满,就入队。已满,就是任务数已经达到maximumPoolSize最大值,就拒绝入队。
取任务
workQueue.take(); 这个下面细说。
这里没有写线程回收,ThreadPoolExecutor
的 allowCoreThreadTimeOut
默认也是不回收的, 可见占用的系统资源很少, 应该还没有你频繁创建线程的和回收消耗。 因为通常将处于阻塞状态的进程排成一个队列,
称为阻塞队列。在有的系统中, 按阻塞的原因不同而将处于阻塞状态的进程排成多个队列。
/**
* 新建线程和添加任务
* @param task
*/
public void execute(Runnable task) {
//判断当前线程,是否启动到最大。(threadSize)
if (threadCount.get() < threadSize) {
int count = threadCount.incrementAndGet();
System.out.println("count:" + count);
//启动多个线程去取任务。核心也在这里。
startThread(count);
}
int workQueueSize=workQueue.size();
if(workQueueSize>maximumPoolSize){
System.out.println("超过最大任务数,拒绝添加任务。thread Count:"+workQueueSize);
return;
}
System.out.println("thread Count:" + threadCount.get());
//添加任务,提示:如果队列超过定义队列大小, Queue full 会抛出异常。
workQueue.add(task);
}
取出任务和处理任务
核心在于 workQueue.take()
;,死循环去取任务,没有任务就阻塞。
取出任务后,调用task.run()
,处理任务。
Thread thread = new Thread() {
@Override
public void run() {
super.run();
while (true) {
try {
//没有任务就阻塞,这里用BlockingQueue,具体看你传递过来的类型。
Runnable task = workQueue.take();
System.out.println("workQueue size:"+workQueue.size() + "::" + getName() + "--:" + task.toString());
//模拟处理任务,和耗时。
task.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
thread.setName("tag:" + nameTag);
thread.start();
到此,核心代码就全部编写完成了。是不是很简单。去掉注释,目测不到五十行。
测试类的代码
来让我们编写测试类的代码:
定义核心线程数为3
定义最大任务数为10,设置小,为了方便观察拒绝任务的添加。
阻塞队列为 LinkedBlockingQueue,且无界。
完毕撒花。
SimpleThreadPoolExecutor executor=new SimpleThreadPoolExecutor(3,10,new LinkedBlockingQueue<>());
//添加一个任务
executor.execute(() -> {
//模拟耗时处理任务
processTask();
});
/**
* 模拟处理任务
*/
private static void processTask() {
//随机休眠
int sleep = new Random().nextInt(10) * 1000;
System.out.println("模拟处理任务耗时:"+sleep);
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
源码地址
总结
好了现在你也会手写一个简单的线程池了, 虽然简单,这不麻雀虽小,但五脏俱全。
本人知识有限,如有描述错误之处,愿虎正。
你看这个像不像你欠我的赞。