前言进程和线程的关系相信大家都知道,这里就不过多解释了。由于一个进程是由多个线程组成的,所以线程池是由若干个线程队列组成的。在并发比较高的情况下,我们通常会创建一个线程池来执行任务,而不是创建多个线程来执行任务,因为创建线程的一系列动作是需要资源开销的,如果频繁创建和销毁线程,其实就是浪费资源,更谈不上提高效率了。一般会创建线程池来统一管理线程,也会引入阻塞和非阻塞队列来接收需要排队处理的任务。但是,线程池中的线程是不是处理完任务就被销毁了呢?事实上,情况并非如此。下面分析一下线程在线程池中是如何复用的。使用线程池使用线程池的原因1、复用创建的线程,减少线程创建和销毁的成本。2、线程池的线程数可以根据自身系统的承载能力合理控制。3、控制并发数,保护系统。privatestaticvoidcreatThreadPool()throwsInterruptedException{ListthreadList=newArrayList<>();longstart=System.currentTimeMillis();log.info("创建线程池开始");for(inti=0;i<100;i++){Threadthread=newThread(()->{try{TimeUnit.SECONDS.sleep(10);}catch(InterruptedExceptione){e.printStackTrace();}},"thread"+i);thread.start();threadList.add(线程);TimeUnit.MILLISECONDS.sleep(1);}longend=System.currentTimeMillis();longneedTime=end-start;log.info("Create100threadsTimespent:"+needTime+"ms");}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(核心线程数),任务会被放入队列。3.队列满,当前运行线程数MaxImumPoolSize(最大线程数),使用Handler拒绝策略,当然不能丢弃任务,一般使用CallerRunsPolicy来使用调用线程执行任务。4、当前线程不需要执行任务,不能一直占用资源。如果超过keepAliveTime后运行的线程数超过了corePoolSize,线程就会被回收。这主要是为了控制核心线程中的线程数。对于线程复用,先看ThreadPoolExecutor的源码,execute线程池执行入口publicvoidexecute(Runnablecommand){if(command==null)thrownewNullPointerException();//当前线程数小于核心线程数intc=ctl.get();if(workerCountOf(c)largestPoolSize)largestPoolSize=s;添加的工人=真;}}最后{mainLock.unlock();}if(workerAdded){t.start();workerStarted=真;}}}finally{if(!workerStarted)addWorkerFailed(w);}returnworkerStarted;复制代码Worker是final修饰的内部类,也就是说不能被其他类继承,所以只能在这个类中进行线程复用,然后再看Worker的run方法中执行的runWorker方法,这个是线程复用的核心方法finalvoidrunWorker(Workerw){Threadwt=Thread.currentThread();//获取线程中执行的任务Runnabletask=w.firstTask;w.firstTask=null;w.unlock();//允许中断booleancompletedAbruptly=true;try{//如果任务不为空||在线程中重新获取任务while(task!=null||(task=getTask())!=null){w.lock();//如果池正在停止,确保线程被中断;//如果不是,确保线程没有被中断。这//需要在第二种情况下重新检查以处理//shutdownNowracewhileclearinginterrupt//判断线程的状态并执行相应的拒绝策略if((runStateAtLeast(ctl.get(),STOP)||(Thread.interrupted()&&runStateAtLeast(ctl.get(),STOP)))&&!wt.isInterrupted())wt.interrupt();尝试{beforeExecute(wt,task);Throwable抛出=null;尝试{任务。跑步();}catch(RuntimeExceptionx){抛出=x;扔x;}赶上(错误x){抛出=x;扔x;}catch(Throwablex){抛出=x;抛出新错误(x);}最后{afterExecute(任务,抛出);}}最后{task=null;w.completedTasks++;w.解锁();}}completedAbruptly=false;}finally{processWorkerExit(w,completedAbruptly);}复制getTask方法的代码重新获取线程中的任务,之前的一系列判断主要是检查线程的状态和线程数线程池中的线程。核心是线程数是否超过核心线程数。超过则进入workQueue工作队列。workQueue.poll非核心线程会一直去工作队列中获取任务。当非核心线程满了,workQueue.take()核心线程就会去获取任务。之前的runWorker方法有一个while循环,所以会一直循环执行,把task拿走。如果此时工作队列中没有队列如果线程存活时间超过keepAliveTime,还没有收到任务,对应的线程就会被销毁privateRunnablegetTask(){booleantimedOut=false;//最后一次poll()是否超时?for(;;){intc=ctl.get();intrs=runStateOf(c);//仅在必要时检查队列是否为空。if(rs>=SHUTDOWN&&(rs>=STOP||workQueue.isEmpty())){decrementWorkerCount();返回空值;}intwc=workerCountOf(c);//工人会被剔除吗?布尔计时=allowCoreThreadTimeOut||wc>核心池大小;if((wc>maximumPoolSize||(timed&&timedOut))&&(wc>1||workQueue.isEmpty())){if(compareAndDecrementWorkerCount(c))返回空;继续;}try{Runnabler=timed?workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS):workQueue.take();如果(r!=null)返回r;超时=真;}catch(InterruptedException重试){timedOut=false;}}}复制代码总在日常开发中,对线程序池的优化也很重要。如果线程池中的核心线程数和最大线程数不是任意定义的,在某些情况下还是需要结合服务器CPU的情况和阻塞队列的使用来缓解线程的压力。阻塞队列本身具有阻塞和唤醒的功能。阻塞队列的长度也需要根据实际业务场景来定义。最后,用好线程池是应对高并发业务场景的关键技术。