前言 进程和线程的关系相信大家都知道,这里我就不做过多的解释了,既然一个进程是由多个线程组成的,那么线程池又是由若干个线程队列组成的,在并发量比较高的情景下,我们通常会去创建线程池就执行任务,而不单一的创建多个线程去执行任务,因为线程的创建的一系列动作,是需要资源开销的,如果频繁的对线程创建销毁,其实本身是一种很浪费资源的,就更谈不上提高效率了。 一般都会创建线程池将线程统一管理,并且还会引入阻塞和非阻塞队列,接收需要排队处理的任务,但是线程池里的线程是在处理完任务就会进行销毁么,其实并不是这样的,下面我们一起来对线程池里线程是如何复用的进行分析。使用线程池使用线程池原因 1。复用已创建的线程,减少线程创建、销毁开销。2。可以根据自身系统的承载能力,合理对线程池线程数量进行控制。3。控制并发数,保护系统。privatestaticvoidcreatThreadPool()throwsInterruptedException{ListThreadthreadListnewArrayList();longstartSystem。currentTimeMillis();log。info(创建线程池开始);for(inti0;i100;i){ThreadthreadnewThread((){try{TimeUnit。SECONDS。sleep(10);}catch(InterruptedExceptione){e。printStackTrace();}},threadi);thread。start();threadList。add(thread);TimeUnit。MILLISECONDS。sleep(1);}longendSystem。currentTimeMillis();longneedTlog。info(创建100个线程所花费的时间:needTimems);}publicstaticvoidmain(String〔〕args)throwsInterruptedException{creatThreadPool();}复制代码 创建100个线程需要264ms,平均一个线程的创建需要2。2ms左右,线程执行任务可以只需要不到1ms,那么这样看来创建线程是不划算的。 这里除里JDK自带的四种线程池类型,简单介绍下jdk自带四种线程池。 1。newCachedThreadPool:可缓存的的无界线程池,可以自动线程回收,通常执行短期异步的任务。 2。newFixThreadPool:固定数量的线程池,控制并发数。 3。newSingleThreadPool:单线程工作的线程池,所有任务按照FIFO先进先出的原则执行。 4。newScheduleThreadPool:可定期执行的线程池,可指定执行时间和执行次数。 通常情况下,在阿里的开发手册上写不推荐使用Executors创建线程,也就是线程池的顶级接口,jdk自带的线程池创建的时候是没有核心线程数的,不断的创建对象,那么就会存在内存溢出的风险。线程池的工作流程 一般创建线程池还是使用ThreadPoolExecutor创建,它的上接口是ExecutorService,所有说真正创建线程池是用ExecutorService创建。7大核心参数这里就不多说了,直接说线程池的工作流程。 1。首先当运行的线程池corePoolSize(核心线程数),就会创建线程执行这个任务 2。线程池线程数corePoolSize(核心线程数),任务放入队列。 3。队列已满,当前运行的线程数MaxImumPoolSize(最大线程数),创建非核心线程数执行任务,如果运行的线程数MaxImumPoolSize(最大线程数),使用Handler拒绝策略,当然不能丢弃任务,一般使用CallerRunsPolicy使用调用线程执行任务。 4。当前线程不需要执行任务,也不能让它一直存在着占用资源,超出keepAliveTime,运行线程数corePoolSize,这线程会被回收掉,这样做主要是控制核心线程里线程数量。线程复用 首先看ThreadPoolExecutor源码,execute线程池执行入口publicvoidexecute(Runnablecommand){if(commandnull)thrownewNullPointerException();当前线程数小于核心线程数intcctl。get();if(workerCountOf(c)corePoolSize){if(addWorker(command,true))cctl。get();}加入等待队列里排队处理if(isRunning(c)workQueue。offer(command)){intrecheckctl。get();检查工作线程停止工作是否需要移除,触发拒绝策略if(!isRunning(recheck)remove(command))reject(command);二次检查elseif(workerCountOf(recheck)0)addWorker(null,false);}无法提交线程则触发拒绝策略elseif(!addWorker(command,false))reject(command);}复制代码 这里看到每个if判断里都存在addWorker方法,那么这个方法肯定是线程是否复用的重点,Wtry{将任务放到Worker工作线程里面,wnewWorker(firstTask);finalThreadtw。if(t!null){finalReentrantLockmainLockthis。mainLmainLock。lock();try{Recheckwhileholdinglock。BackoutonThreadFactoryfailureorifshutdownbeforelockacquired。intrsrunStateOf(ctl。get());if(rsSHUTDOWN(rsSHUTDOWNfirstTasknull)){if(t。isAlive())precheckthattisstartablethrownewIllegalThreadStateException();hashset集合里存放Worker对象workers。add(w);intsworkers。size();if(slargestPoolSize)largestPoolSworkerA}}finally{mainLock。unlock();}if(workerAdded){t。start();workerS}}}finally{if(!workerStarted)addWorkerFailed(w);}returnworkerS复制代码 Worker是个final修饰的内部类,意味着不能被其他类继承,那么线程复用只能在这一个类里面进行,接着看Worker的run方法里面执行的runWorker方法,这个是线程复用的核心方法。finalvoidrunWorker(Workerw){ThreadwtThread。currentThread();获取线程里面执行的任务Runnabletaskw。firstTw。firstTw。unlock();allowinterruptsbooleancompletedAtry{如果任务不为空重新拿取线程里的任务while(task!null(taskgetTask())!null){w。lock();Ifpoolisstopping,ifnot,ensurethreadisnotinterrupted。ThisrequiresarecheckinsecondcasetodealwithshutdownNowracewhileclearinginterrupt判断线程的状态,并执行对应的拒绝策略if((runStateAtLeast(ctl。get(),STOP)(Thread。interrupted()runStateAtLeast(ctl。get(),STOP)))!wt。isInterrupted())wt。interrupt();try{beforeExecute(wt,task);Ttry{task。run();}catch(RuntimeExceptionx){}catch(Errorx){}catch(Throwablex){thrownewError(x);}finally{afterExecute(task,thrown);}}finally{w。completedTw。unlock();}}completedA}finally{processWorkerExit(w,completedAbruptly);}复制代码 getTask方法重新拿取线程里的任务,前面一系列的判断主要是来检查线程的状态,以及线程池线程的数量,其核心主要是线程数量是否超过了核心线程数,如果超过了则会进入workQueue工作队列,workQueue。poll非核心线程会一直去工作队列里获取任务,非核心线程已经满了,则会workQueue。take()核心线程去获取任务,前面的runWorker方法是有while循环的,这样就会一直执行下去,循环拿取任务,如果这个时候工作队列里面没有队列,超过keepAliveTime线程存活时间还没有拿到任务,则会对对应线程进行销毁。privateRunnablegetTask(){booleantimedODidthelastpoll()timeout?for(;;){intcctl。get();intrsrunStateOf(c);Checkifqueueemptyonlyifnecessary。if(rsSHUTDOWN(rsSTOPworkQueue。isEmpty())){decrementWorkerCount();}intwcworkerCountOf(c);Areworkerssubjecttoculling?booleantimedallowCoreThreadTimeOutwccorePoolSif((wcmaximumPoolSize(timedtimedOut))(wc1workQueue。isEmpty())){if(compareAndDecrementWorkerCount(c))}try{Runnablertimed?workQueue。poll(keepAliveTime,TimeUnit。NANOSECONDS):workQueue。take();if(r!null)timedO}catch(InterruptedExceptionretry){timedO}}}复制代码总结 在日常开发中,对线程池的优化也是比较重要的,如果线程池的核心线程数和最大线程数都不是随意定义的,还是要结合本身服务器cpu的情况,以及阻塞队列的使用,在一定情况下能缓解线程的压力,本身阻塞队列是带有阻塞和唤醒的功能,阻塞队列的长度也是需要根据实际开大的业务场景去定义的,最后运用好线程池,在处理高并发的业务场景下还是尤为关键的一项技术。