多线程
来源:互联网 发布:nginx 禁止外网访问 编辑:程序博客网 时间:2024/06/02 01:59
在多线程 - ThreadPoolExecutor详细介绍中对ThreadPoolExecutor进行了介绍,其中有介绍到线程池执行任务的整个过程,本篇文章主要是从源码的角度上探究线程池是如何执行任务的。
执行步骤
先回顾一下多线程 - ThreadPoolExecutor详细介绍中提到的线程池执行任务的步骤:
步骤1:如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务,而不会把这个任务添加到任务队列中;
步骤2:如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务插入任务队列中,不会马上执行该任务,等待运行的线程执行完当前的任务空闲后,再从任务队列中取出任务继续执行。
步骤3:如果这时候任务队列满了,无法继续插入到任务队列,如果此时正在运行的线程数量小于 maximumPoolSize,那么还是会创建一个线程来运行这个任务;
步骤4:如果任务队列满了,并且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”,通过异常处理handler进行处理。
当然上述步骤不是完全的步骤,当在执行过程中发现线程池的状态为非RUNNING状态时,则抛出异常,拒绝此任务,交给异常处理handler进行处理;
源码分析
1.ExecutorService 与 Executors
创建并提交一个任务给线程池执行
ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new TestRunnable());
其中ExecutorService是一个接口。其 继承了Executor接口,Executor定义了一个executor(Runnable command)
public interface Executor { void execute(Runnable command);}
ExecutorService定义了shutdown()、isTerminated()等一些线程池相关的操作
接着我们看Executors这个类,它是一个帮助类,提供了线程池的创建等静态方法
//创建一个核心线程数等于最大线程数的线程池,相当于每提交一个任务,当小于核心线程数量就创建//一个线程执行,不然就等待核心线程池空闲 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }//核心线程数为0,任务队列长度为0,相当于每提交一个任务就创建一个线程执行 public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }//核心线程池和最大线程数量都为1,相当于是当线程执行任务 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
2.ThreadPoolExecutor
execute(Runnable command):提交任务到线程池
通过上面的可以知道,调用Executors的静态方法创建线程池(其他线程池本篇先不分析),其内部调用创建ThreadPoolExecutor,因此提交一个任务给线程池最终执行的是ThreadPoolExecutor的execute(Runnable command)方法,分析execute(Runnable command)的代码
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { //当前的工作线程数量小于核心线程数,则创建一个线程(Worker)执行任务 //若创建线程(Worker)成功,则返回 if (addWorker(command, true)) return; c = ctl.get(); } //若当前的工作线程数量大于等于核心线程数 或者 上面创建线程(Worker)失败 //则判断线程池是否处于运行状态,若运行状态则将任务添加到任务队列 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //再次检查线程池是否处于运行状态,(double-check) //若不处于运行状态,则将刚刚添加的任务移除任务队列,并调用reject()拒绝执行任务 //若处于运行状态则检测当前的工作线程数是不是为0,若为0则调用addWorker()创建一个线程执行任务 if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } //如果线程池不是处于运行状态 或者 往任务队列中添加任务失败后, //尝试通过addWorker创建新的线程执行任务,若创建新的线程失败后,调用reject()拒绝执行任务 else if (!addWorker(command, false)) reject(command); }
其中注释有提到三个过程:
1.当线程池工作线程数小于核心线程数时,会通过addWorker()创建线程执行,其中addWorker()会检查当前线程池的状态以及工作线程池数量,若线程池不处于运行状态或者这期间工作线程数量等于或者大于核心线程数时,会返回false表示失败,然后执行第2步
2.当成功地将任务添加到了任务队列后,依然需要进行double-check来检查两种情况,其一执行到当前位置时线程池是不是被shut down(非运行状态),因为shut down状态下线程池是不接受新的任务的,因此需要把上面的任务从队列中移出去;其二,当执行的这里的时候,需要检查当前线程池中的工作线程数量是不是为0,因为在核心线程数为0,有可能在执行到这里的时候,所有的工作线程都超时导致线程都die了,需要创建新的线程执行。
3.当任务添加到队列失败后,需要尝试创建一个新的线程执行任务,若创建新的线程失败后,这个时候若不是线程池处于非运行状态或者工作线程数量已经达到了最大线程数,因此拒绝此任务。
addWorker(Runnable firstTask, boolean core):创建线程并执行任务
//core 表示是否创建核心线程 private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { //获取线程池工作线程数量,以及线程池状态 int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. //这个可以简化下: //rs > SHUTDOWN || ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()) //这里两种情况拒绝执行任务: //1.线程池处于STOP、TIDYING或TERMINATED中的任意一种状态 //2.当线程池处于SHUTDOWN,如果firstTask不为null 或者 workQueue 为空的情况下,拒绝执行, //也就是 只有firstTask不为null 且workQueue不为空才不会return,能继续往下面执行 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); //如果当前的工作线程数大于容量 或者 //如果创建的是核心线程,若当前的工作线程大于等于核心线程数 或者 //如果创建的是普通线程,若当前的工作线程大于等最大线程数 //拒绝执行此任务 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //CAS操作,线程安全,+1成功之后,说明可以创建线程,跳出循环 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl //再次检查线程池状态是否发生改变,若发生改变再次循环执行 if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } //执行到这里,说可以创建新的线程 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //通过Worker创建新的线程 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { //创建线程成功 //重入锁,线程同步 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //此处rs == SHUTDOWN && firstTask == null 又出现了,下面会说出为什么要这样判断 //创建线程 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w);//将创建的worker加入工作线程队列 int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; //标识创建工作线程成功 workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //开始启动创建的工作线程 t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
通过上面的分析,有一个点需要特别注意一下,! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
这个判断,在上面的注释中说明了,当线程池状态为SHUTDOWN,只有当firstTask == null且workQueue不为空时,才能继续执行,不然上面的条件成立,直接return false了。还记得上一篇文章多线程 - ThreadPoolExecutor详细介绍介绍了在线程池为SHUTDOWN的时候,不会接受新的任务,但是会把任务队列里面的任务全部执行完;此处线程池若处于SHUTDOWN状态,firstTask不为null,则说明是新加入的任务拒绝执行,当firstTask为null时,workQueue为空也会return,不会继续往下执行,因为workQueue已经为空了,且不能加入新的任务,也没有必要继续创建线程了,因此才会有! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
这个判断。在后面创建线程之后又有一个判断 rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)
,在线程池的状态为SHUTDOWN时,只有 firstTask == null 才能将创建的工作线程加入到工作线程队列中,才能算是真正完成了线程的创建。
那么什么时候firstTask == null呢?还记得execute(Runnable command)中,当将任务加入到队列后,若判断当前的工作线程数量为0时,会调用addWorker(null, false);创建一个线程;那么Worker内部又是如何工作的呢?
Worker:工作线程
先看看Worker的代码,下面贴出核心的代码进行分析
//实现了Runnable接口private final class Worker extends AbstractQueuedSynchronizer implements Runnable { Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; //通过线程工厂创建一个线程,并将自身作为参数传进去 this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ //调用runWorker public void run() { runWorker(this); }}
通过刚刚上面addWorker()的分析,最后调用worker.thread线程的start(),由上面Worker的代码可以知道,start()最终执行的是runWorker(this);
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); //获取创建Worker时传进来的任务 Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //当任务task为空的时候,会通过getTask()获取任务 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 //当线程池的状态至少为STOP的时候,中断所有线程 //因为状态为STOP后,不再继续处理任务队列中的任务 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时传入的任务为null时,会通过getTask()获取任务,当getTask()为null的时候,不满足while循环,线程执行完成终止。
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. //这个判断可以简化下 if (rs > SHUTDOWN || (rs == SHUTDOWN && workQueue.isEmpty())) //1.当线程池状态为SHUTDOWN且任务队列为空时 //2.当线程池状态为STOP或者以上时, //工作线程数减1,返回null,线程退出 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? //判断是否超时,若没有设置核心线程超时的话,当当前工作线程数大于核心线程数时,才会读取任务超时 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //1.当工作线程数大于最大线程数,立即减少工作线程数,退出线程,不进行任务读取 //2.当工作线程数大于核心线程数,但是小于等于最大工作线程数时,会读取任务,等待读取超时 //3.当前线程数大于1或者任务队列为空,说明当前只要任务队列不为空,至少要保持一个线程 //工作线程数减1,返回null,使线程退出 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { //workQueue为阻塞队列,当需要判断超时的时候才会有获取任务超时,不然会一直阻塞 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //成功取出任务,并返回 if (r != null) return r; //执行到这里说明是这一次读取任务超时了 timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
通过上面的分析可以知道,
当rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())
成立,会直接返回null,不进行任务读取,退出线程;此种情况就是线程池状态至少为STOP或者为SHUTDOWN且workQueue为空的情况下;
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
判断是否超时,若没有设置核心线程超时的话,当当前工作线程数大于核心线程数时,才会读取任务超时,不然一直阻塞等待;
(wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())
1.当工作线程数大于最大线程数,立即减少工作线程数,退出线程,不进行任务读取
2.当工作线程数大于核心线程数,但是小于等于最大工作线程数时,会读取任务,等待读取超时
3.wc > 1 || workQueue.isEmpty()
当前线程数大于1或者任务队列为空,说明只要任务队列不为空,线程池至少会保留一个工作线程
当firstTast为null时,会通过getTask()从任务队列中读取任务,因此addWorker(null,false)中传入的任务为null,相当于创建了一个新的线程直接从任务队列中进行读取任务执行;
总结
1.ExecutorService 与 Executor为接口,Eexcutors工具类,提供静态的函数构造ThreadPoolExecutor,最终线程池是通过ThreadPoolExecutor来实现的;
2.通过源码分析可知,当线程池为SHUTDOWN状态时,不会接受新的任务,就会执行完任务队列中的任务;当状态至少为STOP时,既不接受新的任务也不执行任务队列中的任务;
3.通过addWorker(null,false)可以创建一个新的线程来执行任务队列中的任务,前提是当前的工作线程数小于最大线程数时;
4.线程池中的非核心线程的等待时间是通过在阻塞队列中读取任务时进行等待来实现的;
5.当任务队列不为空时,线程池至少要保留一个线程。
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 多线程
- 怎么看地图?还是要一起看看地图是怎么画出来的!
- Cocos2d-lua 初识shader之四:描边
- 各种重启
- Matlab中查看内存环境的方法
- 元素出栈、入栈顺序的合法性
- 多线程
- python-PIL
- java POI excel导出,并合并单元格设置宽度高度
- 《将博客搬至CSDN》
- x264学习笔记(一)
- retrofit + RXJava上传 JsonBean、List<T>数据
- hdu 6214 割边最少的最小割
- Android:最全面的 Webview 详解
- 迪杰斯特拉算法