线程池原理

线程池执行过程

一、线程池的执行过程

定义一个目标任务类,一般是一个线程

public class Task implements Runnable {

    int k;

    public Task(int k) {

        this.k = k;

    }

    public void run() {

        for (int i = 0; i < 10; i++) {

            System.out.println("当前执行:" + k +"----"+Thread.currentThread().getName());

        }

    }

}

定义调用者:一般用Executors工厂方法

public class ExecutorTest {

    public static void main(String args[]) {

 //     ExecutorService executorService = Executors.newCachedThreadPool();

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 100; i++) {

            Task task = new Task(i);

            executorService.execute(task);

        }

    }

}

结果:

��ǰִ�У�0----pool-1-thread-1

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�1----pool-1-thread-2

��ǰִ�У�2----pool-1-thread-3

通过运行结果我们可以看到,当用newCachedThreadPool时,会创建很多线程,几乎是一个任务对应一个线程,而当用newFixedThreadPool时,如果任务数量不多,只会有3个线程在执行。

这里我们要引入一个概念:工作线程(workers,即线程池创建的线程,从ThreadPool源码中可以看到是一个HashSet)和任务线程(即目标任务,如实例中的Task类)。

我们看一下ThreadPool的源码:


WorkerThreadPool的内部类,实现了Runnable接口。当我们的ThreadPool创建一个线程时创建的就是WorkerThreadPool持有了一个HashSet来保存worker,名字叫workers

现在我们介绍下主要的参数:

corePoolSize: 核心池大小。就相当于workers的大小。

maximumPoolSize: 最大池大小。相当于workers的极限值。

poolSize: 当前池大小。相当于当前workerssize

当我们用ExecutorService.execute(new Task()) 这段代码来执行一个任务的时候,做了如下几件事。

Step1execute方法:

    public void execute(Runnable command) {

        if (command == null)

            throw new NullPointerException();

        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {

         //加入第一次进入execute方法,poolSize=0corePoolSize=3,会执行addIfUnderCorePoolSize方法,此方法保证如果小于corePoolSize则创建Worker

            if (runState == RUNNING && workQueue.offer(command)) {

            //如果workers超过了corePoolSize,addIfUnderCorePoolSize会返回false,就进入此循环,主要在workQueue.offer(command)方法上,这时就做入队操作,我们的阻塞队列就排上了用场。FixedThreadPool一般走这两步,对应LinkedBlockingQueue

                if (runState != RUNNING || poolSize == 0)

                       // 当状态为非运行或者第一个核心线程执行

                    ensureQueuedTaskHandled(command);

            }

            else if (!addIfUnderMaximumPoolSize(command))

             // 如果队列不能往里加了,就到了这一步了,继续new 新的worker,直到达到上限,超过上限执行异常策略。cachedThreadPoolcorePoolSize0,因为SynchronousQueue的特性一般只会走这一步,前两步都不会走。

                reject(command); // is shutdown or saturated

        }

    }

execute的源码我们分析出有三种情况:

1. 如果运行的线程少于corePoolSize,Executor始终首选添加新的线程,而不进行排队,即使其他辅助线程是空闲的。

2. 如果运行的线程多于corePoolSize,小于maximumPoolSize,则首选将任务添加到队列,而不添加新的线程。

3. 如果无法将请求加入队列(如队列满),则继续创建新的线程,直到达到阀值(maximumPoolSize),这时会执行拒绝策略进行处理。

用一个通俗的例子可以很好理解这种思想:

一家软件公司,来了一批可盈利的任务,于是招了3个程序员大牛,每个大牛一次只能执行一个任务,当任务超过3个时,有一个队列(如一个程序员一天只能干10件任务,队列尺寸为27),这时来的任务加入工作队列,3个程序员就相当于3worker线程,干完一个任务就去队列中取任务,直到队列处理完毕,3人下班回家,线程池关闭。然而当任务超过队列最大尺寸了,比如一下子来了50个任务,这时三个人已经达到极限了,公司只能再继续招人,但是招人也是有成本的,不能无限招,招多了公司的成本太高了,这就有了阀值的概念(maximumPoolSize),这时比如又招了2个人,达到了阀值(5),如果再有任务进来,公司就会拒绝。

上面的例子其实就是FixedThreadPool的翻版,而对于CachedPoolSize,其实只会走第三步,第二步极少出现(因为SynchronousQueue是一个单一生产者消费者模型,中间对静态资源进行了同步,offerpoll方法必须同时执行时,队列才会有值,这个我们后续再详细介绍)。返回例子,对于CachePoolSize,相当于这个软件公司来一个任务招一个程序员,

当任务执行完毕后去队列中查看,通过polltime, TimeUnit.Second)方法获取任务,延迟60秒,这里就是缓存的地方。而LinkedBlokingQueue直接通过take方法获取任务。如果有任务,拿到之后执行任务,如果没有任务结束,程序员回家,线程池结束。(这个例子有点儿晦涩,我们下边源码分析就明白了)

Step:2:addIfUnderCorePoolSize方法(创建工作线程worker

    private boolean addIfUnderCorePoolSize(Runnable firstTask) {

        Thread t = null;

        final ReentrantLock mainLock = this.mainLock;

        mainLock.lock();

        try {

            if (poolSize < corePoolSize && runState == RUNNING)

                 // 这里,调用addThread方法,创建线程

                t = addThread(firstTask);

        } finally {

            mainLock.unlock();

        }

        if (t == null)

            return false;

        t.start(); //线程执行

        return true;

    }

Step3:addThread

private Thread addThread(Runnable firstTask) {

//1. 创建一个worker对象

        Worker w = new Worker(firstTask);

       //2.通过线程工厂创建一个线程对象,Executors.newFixedThreadPool中都是用的Executors.defaultThreadFactory()

        Thread t = threadFactory.newThread(w);

        if (t != null) {

            w.thread = t;

            workers.add(w); //加入工作队列hashsetworkers

            int nt = ++poolSize; // poolSize加一

            if (nt > largestPoolSize)

                largestPoolSize = nt;

        }

        return t;

    }

Step4: 通过默认线程工程构造线程

  public Thread newThread(Runnable r) {

            Thread t = new Thread(group, r,

                                  namePrefix + threadNumber.getAndIncrement(),

            

                      0);

                  //设置一些线程属性,设为普通用户线程,非守护

            if (t.isDaemon())

                t.setDaemon(false);

                      //统一设置优先级

            if (t.getPriority() != Thread.NORM_PRIORITY)

                t.setPriority(Thread.NORM_PRIORITY);

            return t;

        }

Step5:Worker执行:step2中的start方法执行的是workerrun方法

        public void run() {

            try {

                Runnable task = firstTask;

                firstTask = null;

//循环执行,当前任务执行完毕后,设为null,去getTask,看看队列中有没有任务

                while (task != null || (task = getTask()) != null) {

                    runTask(task); //任务线程执行

                    task = null;

                }

            } finally {

                workerDone(this);

            }

        }

    }

Step5.1:看下getTask方法

    Runnable getTask() {

        for (;;) {

            try {

                int state = runState;

                if (state > SHUTDOWN) //如果是关闭状态不再获取任务

                    return null;

                Runnable r;

                if (state == SHUTDOWN)  // 正在关闭状态,把剩下的任务处理完

                    r = workQueue.poll();

                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)

//如果池中线程数大于核心线程数,任务较多,或允许核心线程池获取任务超时时,调用poll方法,SynchronousQueue走这一步

                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);

                Else

                //任务较少时,直接用take阻塞,BlockingQueue直接走这一步

                    r = workQueue.take();

                if (r != null)

                    return r;

                if (workerCanExit()) {

                    if (runState >= SHUTDOWN) // Wake up others

                        interruptIdleWorkers();

                    return null;

                }

                // Else retry

            } catch (InterruptedException ie) {

                // On interruption, re-check runState

            }

        }

    }

Step5.2:runTask

        private void runTask(Runnable task) {

            final ReentrantLock runLock = this.runLock;

            runLock.lock();

            try {

                /*

                 * Ensure that unless pool is stopping, this thread

                 * does not have its interrupt set. This requires a

                 * double-check of state in case the interrupt was

                 * cleared concurrently with a shutdownNow -- if so,

                 * the interrupt is re-enabled.

                 */

                if (runState < STOP &&

                    Thread.interrupted() &&

                    runState >= STOP)

                    thread.interrupt();

                /*

                 * Track execution state to ensure that afterExecute

                 * is called only if task completed or threw

                 * exception. Otherwise, the caught runtime exception

                 * will have been thrown by afterExecute itself, in

                 * which case we don't want to call it again.

                 */

                boolean ran = false;

                beforeExecute(thread, task);

                try {

                    task.run(); //注意这里,直接就是方法调用

                    ran = true;

                    afterExecute(task, null);

                    ++completedTasks;

                } catch (RuntimeException ex) {

                    if (!ran)

                        afterExecute(task, ex);

                    throw ex;

                }

            } finally {

                runLock.unlock();

            }

        }

Step6workerDone

    void workerDone(Worker w) {

        final ReentrantLock mainLock = this.mainLock;

        mainLock.lock();

        try {

            completedTaskCount += w.completedTasks; //累加完成任务数

            workers.remove(w);//移除worker

            if (--poolSize == 0)

                tryTerminate();

        } finally {

            mainLock.unlock();

        }

    }

基本的流程就是:创建线程池——》创建新线程或加入队列——》

工作线程(workers)开始执行——》 调用目标任务的run方法——》循环查看队列,获取任务——》loop。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值