Java线程池的复用理解

更新 2019-01-15

后来想了一想,觉得最重要的东西没有说,下面的内容我自己隔了一段时间甚至都有些没理解,主要现在想说的最重要的东西,就是线程的start和run,start是启动一个新线程,run是执行线程的方法,复用就是把多个不同的run放啊写到一个线程的run内,然后start这个线程,如果提前写入,其实也不算是复用,线程池实现的是一个动态的复用,就是在线程执行过程中,动态的从一个阻塞队列中拿出任务,添加到线程run方法内,从而实现复用.最重要的就是这个吧.

前情

今天是我第三次看线程池的源码,才敢写下这一点点理解,虽然我的博客一般都是自己学习的笔记和心得,但是毕竟是在互联网上,有需求的同行看到了,还是不能害人.虽说是第三次看,但是对其中一些很细节的条件分支还不是很理解,只是从宏观上稍微理解了作者的一些思路.下面说一下:

池化核心

在我的理解中,池化技术,无论是连接池还是线程池,核心就是复用二字.池化技术往简单了说,就是重复利用一些比较"昂贵"的资源.
Java内的线程池有一系列的实现继承关系,到最高层的实现类是ThreadPoolExecutor类.这个线程池如何实现复用,是我最关心的,但看了之后发现,其实很简单.
这个类实现复用就是用一个内部类Worker,首先,这个内部类实现了Runnable,还继承了AbstractQueuedSynchronizer类,对于后者,暂时只需要知道它是负责Worker内部独占锁机制的,我们注意的还是Runnable,我们都知道正常创建一个线程的几种方法,实现Runnable就是其中一种方式.
实现Runnable并不奇怪,但是实现的同时,Worker内部还有一个Thread类型的属性.这两件事单独看,都很正常,但放在一起,就让我很费解,然后看一下构造器,这里贴一下源码:

 Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

参数很熟悉,和我们使用实现Runnable的类创建线程的道理一样,不管其他,核心是最后一句,
this.thread = getThreadFactory().newThread(this);
这句什么意思,getThreadFactory().newThread()可以看做我们使用的new Thread(),看参数是this,也就是用Worker创建一个线程,然后指向Woker内部的Thread.

到这里,我想说一下我的学习时的疑惑,我再想,为什么要这样写,没想通,我换了个方向,如果不这样写,我又没有别的方法实现复用,别的方法和源码方法对立,那就是把thread和runnable实现分开写,从线程池的角度来看,我需要创建一个类似Worker类但是没有实现Runnable接口,实现runnable实现作为任务存放在阻塞队列中,然后我的类Worker类构造器也类似,不过不在将this作为创建线程的参数,而是使用传入的runnable.如何启动呢?使用类worker.thread.start()吗?这只能执行传入的那个,就算我再指定我的类Worker内部的runnable任务,我该怎么知道上一个线程是否执行完,在外部写一个死循环,在循环内部不断执行当前任务,执行完毕后再从队列中获取等待的任务,再执行吗?又有了问题,这个循环写在哪里?是不是每一个类Worker都得一个循环?这个时候我突然意识到一件事,如果我这样写,每次的任务如何执行,还是thread.start()吗?根据我的理解,start()是执行一个线程,它和直接运行runnable实现内的run方法是不同的,start涉及到操作系统的系统调用,它会启动一个新线程,我这样写根本没有实现复用.这样,我才深深的理解了源代码写法的深意.

源代码中之所以如此写,是我暂时想到实现复用的唯一方法,将整个Worker作为一个任务提交到本对象中的Thread对象执行,这时我在Worker的run方法中执行多个任务的run方法,这才是复用.

胡扯了这么多,可能大家忽略了Worker实现runnable,那就必须重写run方法,看一下源代码:
public void run() { runWorker(this); }
就一行,runWorker(this),这样什么都看不出来,点进去;

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

其实这段代码不难,但是很多变量必须结合上下文,所以我就简单说一下这段代码干了什么,
首先,拿到Worker对象中的实例化时传入的firstTask,顾名思义,不解释了,然后进入一个循环,循环内部,就是执行当前task.run方法,也就是说真正的执行任务,需要注意的是,每次循环中,是被上了锁的,是独占的,执行完当前任务时,执行getTask(),从任务队列中拿到任务,再次执行,直到没有任务才关闭这个线程.

这篇博客暂时就到这里了,我没有对整个线程池进行源码解析,我也没那个能力,只是说说我自己对复用的理解,希望可以帮助到有次困惑的同行.

猜你喜欢

转载自blog.csdn.net/qq_36865108/article/details/85553739