参考线程池源码,用100行代码写一个线程池 | Java Debug笔记

1,951 阅读3分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接

个人唠叨

我把一篇文章劈成7篇,幸运的是,它们都活了下来,并参加了掘金的Java 笔记debug活动。听闻如果再次把它合到一起,可能会变成一个switch。但我发现它们合在一起成功率只有0.0001。

前言

网上的对线程池源码的分析太多了,这里就不说原理了。直接参考它写一个简单的线程池吧。我记得google 的volley 写的更简单暴力,就直接for循环出来几个线程,然后阻塞读取任务。

开始循序渐进....记得点赞额(⊙o⊙)…

强烈建议先看前面这两文章

敲重点

Java运算符扫盲

JAVA线程池源码之深入状态值分析

先画个流程图

我们按照流程图,编写代码。

简单的线程池原型 (2).jpg

核心线程处理类

我们先定义几个变量。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(); 这个下面细说。

这里没有写线程回收,ThreadPoolExecutorallowCoreThreadTimeOut 默认也是不回收的, 可见占用的系统资源很少, 应该还没有你频繁创建线程的和回收消耗。 因为通常将处于阻塞状态的进程排成一个队列, 称为阻塞队列。在有的系统中, 按阻塞的原因不同而将处于阻塞状态的进程排成多个队列。


/**
     * 新建线程和添加任务
     * @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();
    }

}


源码地址

源码地址,简单的线程池

总结

好了现在你也会手写一个简单的线程池了, 虽然简单,这不麻雀虽小,但五脏俱全。

本人知识有限,如有描述错误之处,愿虎正。

你看这个像不像你欠我的赞。