幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

合理使用线程池以及线程变量

  背景
  随着计算技术的不断发展,3纳米制程芯片已进入试产阶段,摩尔定律在现有工艺下逐渐面临巨大的物理瓶颈,通过多核处理器技术来提升服务器的性能成为提升算力的主要方向。
  在服务器领域,基于java构建的后端服务器占据着领先地位,因此,掌握java并发编程技术,充分利用CPU的并发处理能力是一个开发人员必修的基本功,本文结合线程池源码和实践,简要介绍了线程池和线程变量的使用。
  线程池概述什么是线程池
  线程池是一种池化的线程使用模式,通过创建一定数量的线程,让这些线程处于就绪状态来提高系统响应速度,在线程使用完成后归还到线程池来达到重复利用的目标,从而降低系统资源的消耗。为什么要使用线程池
  总体来说,线程池有如下的优势:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  线程池的使用线程池创建核心参数设置
  在java中,线程池的实现类是ThreadPoolExecutor,构造函数如下:publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnittimeUnit,BlockingQueueRunnableworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)
  可以通过newThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler)来创建一个线程池。corePoolSize参数
  在构造函数中,corePoolSize为线程池核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程超时也会回收。maximumPoolSize参数
  在构造函数中,maximumPoolSize为线程池所能容纳的最大线程数。keepAliveTime参数
  在构造函数中,keepAliveTime表示线程闲置超时时长。如果线程闲置时间超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。timeUnit参数
  在构造函数中,timeUnit表示线程闲置超时时长的时间单位。常用的有:TimeUnit。MILLISECONDS(毫秒)、TimeUnit。SECONDS(秒)、TimeUnit。MINUTES(分)。blockingQueue参数
  在构造函数中,blockingQueue表示任务队列,线程池任务队列的常用实现类有:ArrayBlockingQueue:一个数组实现的有界阻塞队列,此队列按照FIFO的原则对元素进行排序,支持公平访问队列。LinkedBlockingQueue:一个由链表结构组成的可选有界阻塞队列,如果不指定大小,则使用Integer。MAXVALUE作为队列大小,按照FIFO的原则对元素进行排序。PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列,默认情况下采用自然顺序排列,也可以指定Comparator。DelayQueue:一个支持延时获取元素的无界阻塞队列,创建元素时可以指定多久以后才能从队列中获取当前元素,常用于缓存系统设计与定时任务调度等。SynchronousQueue:一个不存储元素的阻塞队列。存入操作必须等待获取操作,反之亦然。LinkedTransferQueue:一个由链表结构组成的无界阻塞队列,与LinkedBlockingQueue相比多了transfer和tryTranfer方法,该方法在有消费者等待接收元素时会立即将元素传递给消费者。LinkedBlockingDeque:一个由链表结构组成的双端阻塞队列,可以从队列的两端插入和删除元素。threadFactory参数
  在构造函数中,threadFactory表示线程工厂。用于指定为线程池创建新线程的方式,threadFactory可以设置线程名称、线程组、优先级等参数。如通过Google工具包可以设置线程池里的线程名:newThreadFactoryBuilder()。setNameFormat(generaldetailbatchd)。build()RejectedExecutionHandler参数
  在构造函数中,rejectedExecutionHandler表示拒绝策略。当达到最大线程数且队列任务已满时需要执行的拒绝策略,常见的拒绝策略如下:ThreadPoolExecutor。AbortPolicy:默认策略,当任务队列满时抛出RejectedExecutionException异常。ThreadPoolExecutor。DiscardPolicy:丢弃掉不能执行的新任务,不抛任何异常。ThreadPoolExecutor。CallerRunsPolicy:当任务队列满时使用调用者的线程直接执行该任务。ThreadPoolExecutor。DiscardOldestPolicy:当任务队列满时丢弃阻塞队列头部的任务(即最老的任务),然后添加当前任务。
  线程池状态转移图
  ThreadPoolExecutor线程池有如下几种状态:RUNNING:运行状态,接受新任务,持续处理任务队列里的任务;SHUTDOWN:不再接受新任务,但要处理任务队列里的任务;STOP:不再接受新任务,不再处理任务队列里的任务,中断正在进行中的任务;TIDYING:表示线程池正在停止运作,中止所有任务,销毁所有工作线程,当线程池执行terminated()方法时进入TIDYING状态;TERMINATED:表示线程池已停止运作,所有工作线程已被销毁,所有任务已被清空或执行完毕,terminated()方法执行完成;
  线程池任务调度机制
  线程池提交一个任务时任务调度的主要步骤如下:当线程池里存活的核心线程数小于corePoolSize核心线程数参数的值时,线程池会创建一个核心线程去处理提交的任务;如果线程池核心线程数已满,即线程数已经等于corePoolSize,新提交的任务会被尝试放进任务队列workQueue中等待执行;当线程池里面存活的线程数已经等于corePoolSize了,且任务队列workQueue已满,再判断当前线程数是否已达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务;如果当前的线程数已达到了maximumPoolSize,还有新的任务提交过来时,执行拒绝策略进行处理。
  核心代码如下:publicvoidexecute(Runnablecommand){if(commandnull)thrownewNullPointerException();Proceedin3steps:1。IffewerthancorePoolSizethreadsarerunning,trytostartanewthreadwiththegivencommandasitsfirsttask。ThecalltoaddWorkeratomicallychecksrunStateandworkerCount,andsopreventsfalsealarmsthatwouldaddthreadswhenitshouldnt,byreturningfalse。2。Ifataskcanbesuccessfullyqueued,thenwestillneedtodoublecheckwhetherweshouldhaveaddedathread(becauseexistingonesdiedsincelastchecking)orthatthepoolshutdownsinceentryintothismethod。Sowerecheckstateandifnecessaryrollbacktheenqueuingifstopped,orstartanewthreadiftherearenone。3。Ifwecannotqueuetask,thenwetrytoaddanewthread。Ifitfails,weknowweareshutdownorsaturatedandsorejectthetask。intcctl。get();if(workerCountOf(c)corePoolSize){if(addWorker(command,true))return;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);}
  Tomcat线程池分析Tomcat请求处理过程
  Tomcat的整体架构包含连接器和容器两大部分,其中连接器负责与外部通信,容器负责内部逻辑处理。在连接器中:使用ProtocolHandler接口来封装IO模型和应用层协议的差异,其中IO模型可以选择非阻塞IO、异步IO或APR,应用层协议可以选择HTTP、HTTPS或AJP。ProtocolHandler将IO模型和应用层协议进行组合,让EndPoint只负责字节流的收发,Processor负责将字节流解析为TomcatRequestResponse对象,实现功能模块的高内聚和低耦合,ProtocolHandler接口继承关系如下图示。通过适配器Adapter将TomcatRequest对象转换为标准的ServletRequest对象。
  Tomcat为了实现请求的快速响应,使用线程池来提高请求的处理能力。下面我们以HTTP非阻塞IO为例对Tomcat线程池进行简要的分析。Tomcat线程池创建
  在Tomcat中,通过AbstractEndpoint类提供底层的网络IO的处理,若用户没有配置自定义公共线程池,则AbstractEndpoint通过createExecutor方法来创建Tomcat默认线程池。
  核心部分代码如下:publicvoidcreateExecutor(){internalExecutortrue;TaskQueuetaskqueuenewTaskQueue();TaskThreadFactorytfnewTaskThreadFactory(getName()exec,daemon,getThreadPriority());executornewThreadPoolExecutor(getMinSpareThreads(),getMaxThreads(),60,TimeUnit。SECONDS,taskqueue,tf);taskqueue。setParent((ThreadPoolExecutor)executor);}
  其中,TaskQueue、ThreadPoolExecutor分别为Tomcat自定义任务队列、线程池实现。Tomcat自定义ThreadPoolExecutor
  Tomcat自定义线程池继承于java。util。concurrent。ThreadPoolExecutor,并新增了一些成员变量来更高效地统计已经提交但尚未完成的任务数量(submittedCount),包括已经在队列中的任务和已经交给工作线程但还未开始执行的任务。Sameasajava。util。concurrent。ThreadPoolExecutorbutimplementsamuchmoreefficient{linkgetSubmittedCount()}method,tobeusedtoproperlyhandletheworkqueue。IfaRejectedExecutionHandlerisnotspecifiedadefaultonewillbeconfiguredandthatonewillalwaysthrowaRejectedExecutionExceptionpublicclassThreadPoolExecutorextendsjava。util。concurrent。ThreadPoolExecutor{Thenumberoftaskssubmittedbutnotyetfinished。Thisincludestasksinthequeueandtasksthathavebeenhandedtoaworkerthreadbutthelatterdidnotstartexecutingthetaskyet。Thisnumberisalwaysgreaterorequalto{linkgetActiveCount()}。新增的submittedCount成员变量,用于统计已提交但还未完成的任务数privatefinalAtomicIntegersubmittedCountnewAtomicInteger(0);privatefinalAtomicLonglastContextStoppedTimenewAtomicLong(0L);构造函数publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueRunnableworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);预启动所有核心线程prestartAllCoreThreads();}}
  Tomcat在自定义线程池ThreadPoolExecutor中重写了execute()方法,并实现对提交执行的任务进行submittedCount加一。Tomcat在自定义ThreadPoolExecutor中,当线程池抛出RejectedExecutionException异常后,会调用force()方法再次向TaskQueue中进行添加任务的尝试。如果添加失败,则submittedCount减一后,再抛出RejectedExecutionException。Overridepublicvoidexecute(Runnablecommand){execute(command,0,TimeUnit。MILLISECONDS);}publicvoidexecute(Runnablecommand,longtimeout,TimeUnitunit){submittedCount。incrementAndGet();try{super。execute(command);}catch(RejectedExecutionExceptionrx){if(super。getQueue()instanceofTaskQueue){finalTaskQueuequeue(TaskQueue)super。getQueue();try{if(!queue。force(command,timeout,unit)){submittedCount。decrementAndGet();thrownewRejectedExecutionException(Queuecapacityisfull。);}}catch(InterruptedExceptionx){submittedCount。decrementAndGet();thrownewRejectedExecutionException(x);}}else{submittedCount。decrementAndGet();throwrx;}}}Tomcat自定义任务队列
  在Tomcat中重新定义了一个阻塞队列TaskQueue,它继承于LinkedBlockingQueue。在Tomcat中,核心线程数默认值为10,最大线程数默认为200,为了避免线程到达核心线程数后后续任务放入队列等待,Tomcat通过自定义任务队列TaskQueue重写offer方法实现了核心线程池数达到配置数后线程的创建。
  具体地,从线程池任务调度机制实现可知,当offer方法返回false时,线程池将尝试创建新新线程,从而实现任务的快速响应。TaskQueue核心实现代码如下:Astaskqueuespecificallydesignedtorunwithathreadpoolexecutor。Thetaskqueueisoptimisedtoproperlyutilizethreadswithinathreadpoolexecutor。Ifyouuseanormalqueue,theexecutorwillspawnthreadswhenthereareidlethreadsandyouwontbeabletoforceitemsontothequeueitself。publicclassTaskQueueextendsLinkedBlockingQueueRunnable{publicbooleanforce(Runnableo,longtimeout,TimeUnitunit)throwsInterruptedException{if(parentnullparent。isShutdown())thrownewRejectedExecutionException(Executornotrunning,cantforceacommandintothequeue);returnsuper。offer(o,timeout,unit);forcestheitemontothequeue,tobeusedifthetaskisrejected}Overridepublicbooleanoffer(Runnableo){1。parent为线程池,Tomcat中为自定义线程池实例wecantdoanychecksif(parentnull)returnsuper。offer(o);2。当线程数达到最大线程数时,新提交任务入队wearemaxedoutonthreads,simplyqueuetheobjectif(parent。getPoolSize()parent。getMaximumPoolSize())returnsuper。offer(o);3。当提交的任务数小于线程池中已有的线程数时,即有空闲线程,任务入队即可wehaveidlethreads,justaddittothequeueif(parent。getSubmittedCount()(parent。getPoolSize()))returnsuper。offer(o);4。【关键点】如果当前线程数量未达到最大线程数,直接返回false,让线程池创建新线程ifwehavelessthreadsthanmaximumforcecreationofanewthreadif(parent。getPoolSize()parent。getMaximumPoolSize())returnfalse;5。最后的兜底,放入队列ifwereachedhere,weneedtoaddittothequeuereturnsuper。offer(o);}}Tomcat自定义任务线程
  Tomcat中通过自定义任务线程TaskThread实现对每个线程创建时间的记录;使用静态内部类WrappingRunnable对Runnable进行包装,用于对StopPooledThreadException异常类型的处理。AThreadimplementationthatrecordsthetimeatwhichitwascreated。publicclassTaskThreadextendsThread{privatefinallongcreationTime;publicTaskThread(ThreadGroupgroup,Runnabletarget,Stringname){super(group,newWrappingRunnable(target),name);this。creationTimeSystem。currentTimeMillis();}Wrapsa{linkRunnable}toswallowany{linkStopPooledThreadException}insteadoflettingitgoandpotentiallytriggerabreakinadebugger。privatestaticclassWrappingRunnableimplementsRunnable{privateRunnablewrappedRunnable;WrappingRunnable(RunnablewrappedRunnable){this。wrappedRunnablewrappedRunnable;}Overridepublicvoidrun(){try{wrappedRunnable。run();}catch(StopPooledThreadExceptionexc){expected:wejustswallowtheexceptiontoavoiddisturbingdebuggerslikeeclipseslog。debug(Threadexitingonpurpose,exc);}}}}思考小结Tomcat为什么要自定义线程池和任务队列实现?JUC原生线程池在提交任务时,当工作线程数达到核心线程数后,继续提交任务会尝试将任务放入阻塞队列中,只有当前运行线程数未达到最大设定值且在任务队列任务满后,才会继续创建新的工作线程来处理任务,因此JUC原生线程池无法满足Tomcat快速响应的诉求。
  Tomcat为什么使用无界队列?Tomcat在EndPoint中通过acceptCount和maxConnections两个参数来避免过多请求积压。其中maxConnections为Tomcat在任意时刻接收和处理的最大连接数,当Tomcat接收的连接数达到maxConnections时,Acceptor不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections(maxConnections默认为10000,如果设置为1,则连接数不受限制)。acceptCount为accept队列的长度,当accept队列中连接的个数达到acceptCount时,即队列满,此时进来的请求一律被拒绝,默认值是100(基于Tomcat8。5。43版本)。因此,通过acceptCount和maxConnections两个参数作用后,Tomcat默认的无界任务队列通常不会造成OOM。AllowstheserverdevelopertospecifytheacceptCount(backlog)thatshouldbeusedforserversockets。Bydefault,thisvalueis100。privateintacceptCount100;privateintmaxConnections10000;最佳实践避免用Executors的创建线程池
  Executors常用方法有以下几个:newCachedThreadPool():创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到线程池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。CachedThreadPool适用于并发执行大量短期耗时短的任务,或者负载较轻的服务器;newFiexedThreadPool(intnThreads):创建固定数目线程的线程池,线程数小于nThreads时,提交新的任务会创建新的线程,当线程数等于nThreads时,提交新的任务后任务会被加入到阻塞队列,正在执行的线程执行完毕后从队列中取任务执行,FiexedThreadPool适用于负载略重但任务不是特别多的场景,为了合理利用资源,需要限制线程数量;newSingleThreadExecutor()创建一个单线程化的Executor,SingleThreadExecutor适用于串行执行任务的场景,每个任务按顺序执行,不需要并发执行;newScheduledThreadPool(intcorePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。ScheduledThreadPool中,返回了一个ScheduledThreadPoolExecutor实例,而ScheduledThreadPoolExecutor实际上继承了ThreadPoolExecutor。从代码中可以看出,ScheduledThreadPool基于ThreadPoolExecutor,corePoolSize大小为传入的corePoolSize,maximumPoolSize大小为Integer。MAXVALUE,超时时间为0,workQueue为DelayedWorkQueue。实际上ScheduledThreadPool是一个调度池,其实现了schedule、scheduleAtFixedRate、scheduleWithFixedDelay三个方法,可以实现延迟执行、周期执行等操作;newSingleThreadScheduledExecutor()创建一个corePoolSize为1的ScheduledThreadPoolExecutor;newWorkStealingPool(intparallelism)返回一个ForkJoinPool实例,ForkJoinPool主要用于实现分而治之的算法,适合于计算密集型的任务。
  Executors类看起来功能比较强大、用起来还比较方便,但存在如下弊端:FiexedThreadPool和SingleThreadPool任务队列长度为Integer。MAXVALUE,可能会堆积大量的请求,从而导致OOM;CachedThreadPool和ScheduledThreadPool允许创建的线程数量为Integer。MAXVALUE,可能会创建大量的线程,从而导致OOM;
  使用线程时,可以直接调用ThreadPoolExecutor的构造函数来创建线程池,并根据业务实际场景来设置corePoolSize、blockingQueue、RejectedExecuteHandler等参数。避免使用局部线程池
  使用局部线程池时,若任务执行完后没有执行shutdown()方法或有其他不当引用,极易造成系统资源耗尽。合理设置线程池参数
  在工程实践中,通常使用下述公式来计算核心线程数:
  nThreads(wc)cnu(wc1)nu
  其中,w为等待时间,c为计算时间,n为CPU核心数(通常可通过Runtime。getRuntime()。availableProcessors()方法获取),u为CPU目标利用率(取值区间为〔0,1〕);在最大化CPU利用率的情况下,当处理的任务为计算密集型任务时,即等待时间w为0,此时核心线程数等于CPU核心数。
  上述计算公式是理想情况下的建议核心线程数,而不同系统应用在运行不同的任务时可能会有一定的差异,因此最佳线程数参数还需要根据任务的实际运行情况和压测表现进行微调。增加异常处理
  为了更好地发现、分析和解决问题,建议在使用多线程时增加对异常的处理,异常处理通常有下述方案:在任务代码处增加try。。。catch异常处理如果使用的Future方式,则可通过Future对象的get方法接收抛出的异常为工作线程设置setUncaughtExceptionHandler,在uncaughtException方法中处理异常优雅关闭线程池publicvoiddestroy(){try{poolExecutor。shutdown();if(!poolExecutor。awaitTermination(AWAITTIMEOUT,TimeUnit。SECONDS)){poolExecutor。shutdownNow();}}catch(InterruptedExceptione){如果当前线程被中断,重新取消所有任务pool。shutdownNow();保持中断状态Thread。currentThread()。interrupt();}}
  为了实现优雅停机的目标,我们应当先调用shutdown方法,调用这个方法也就意味着,这个线程池不会再接收任何新的任务,但是已经提交的任务还会继续执行。之后我们还应当调用awaitTermination方法,这个方法可以设定线程池在关闭之前的最大超时时间,如果在超时时间结束之前线程池能够正常关闭则会返回true,否则,超时会返回false。通常我们需要根据业务场景预估一个合理的超时时间,然后调用该方法。
  如果awaitTermination方法返回false,但又希望尽可能在线程池关闭之后再做其他资源回收工作,可以考虑再调用一下shutdownNow方法,此时队列中所有尚未被处理的任务都会被丢弃,同时会设置线程池中每个线程的中断标志位。shutdownNow并不保证一定可以让正在运行的线程停止工作,除非提交给线程的任务能够正确响应中断。鹰眼上下文参数传递在主线程中,开启鹰眼异步模式,并将ctx传递给多线程任务防止鹰眼链路丢失,需要传递RpcContextinnerctxEagleEye。getRpcContext();开启异步模式ctx。setAsyncMode(true);在线程池任务线程中,设置鹰眼rpc环境privatevoidrunTask(){try{EagleEye。setRpcContext(ctx);dosomething。。。}catch(Exceptione){log。error(requestError,params:{},this。params,e);}finally{判断当前任务是否是主线程在运行,当Rejected策略为CallerRunsPolicy的时候,核对当前线程if(mainThread!Thread。currentThread()){EagleEye。clearRpcContext();}}}
  ThreadLocal线程变量概述什么是ThreadLocal
  ThreadLocal类提供了线程本地变量(threadlocalvariables),这些变量不同于普通的变量,访问线程本地变量的每个线程(通过其get或set方法)都有其自己的独立初始化的变量副本,因此ThreadLocal没有多线程竞争的问题,不需要单独进行加锁。ThreadLocal使用场景每个线程都需要有属于自己的实例数据(线程隔离);框架跨层数据的传递;需要参数全局传递的复杂调用链路的场景;数据库连接的管理,在AOP的各种嵌套调用中保证事务的一致性;
  ThreadLocal的原理与实践
  对于ThreadLocal而言,常用的方法有getsetinitialValue3个方法。
  众所周知,在java中SimpleDateFormat有线程安全问题,为了安全地使用SimpleDateFormat,除了1)创建SimpleDateFormat局部变量;和2)加同步锁两种方案外,我们还可以使用3)ThreadLocal的方案:使用ThreadLocal定义一个全局的SimpleDateFormatprivatestaticThreadLocalSimpleDateFormatsimpleDateFormatThreadLocalnewThreadLocalSimpleDateFormat(){OverrideprotectedSimpleDateFormatinitialValue(){returnnewSimpleDateFormat(yyyyMMddHH:mm:ss);}};用法StringdateStringsimpleDateFormatThreadLocal。get()。format(calendar。getTime());ThreadLocal原理
  Thread内部维护了一个ThreadLocal。ThreadLocalMap实例(threadLocals),ThreadLocal的操作都是围绕着threadLocals来操作的。threadLocal。get()方法Returnsthevalueinthecurrentthreadscopyofthisthreadlocalvariable。Ifthevariablehasnovalueforthecurrentthread,itisfirstinitializedtothevaluereturnedbyaninvocationofthe{linkinitialValue}method。returnthecurrentthreadsvalueofthisthreadlocalpublicTget(){1。获取当前线程ThreadtThread。currentThread();2。获取当前线程内部的ThreadLocalMap变量t。threadLocals;ThreadLocalMapmapgetMap(t);3。判断map是否为nullif(map!null){4。使用当前threadLocal变量获取entryThreadLocalMap。Entryemap。getEntry(this);5。判断entry是否为nullif(e!null){6。返回Entry。valueSuppressWarnings(unchecked)Tresult(T)e。value;returnresult;}}7。如果mapentry为null设置初始值returnsetInitialValue();}Variantofset()toestablishinitialValue。Usedinsteadofset()incaseuserhasoverriddentheset()method。returntheinitialvalueprivateTsetInitialValue(){1。初始化value,如果重写就用重写后的value,默认nullTvalueinitialValue();2。获取当前线程ThreadtThread。currentThread();3。获取当前线程内部的ThreadLocalMap变量ThreadLocalMapmapgetMap(t);if(map!null)4。不为null就set,key:threadLocal,value:valuemap。set(this,value);else5。map若为null则创建ThreadLocalMap对象createMap(t,value);returnvalue;}CreatethemapassociatedwithaThreadLocal。OverriddeninInheritableThreadLocal。paramtthecurrentthreadparamfirstValuevaluefortheinitialentryofthemapvoidcreateMap(Threadt,TfirstValue){t。threadLocalsnewThreadLocalMap(this,firstValue);}Constructanewmapinitiallycontaining(firstKey,firstValue)。ThreadLocalMapsareconstructedlazily,soweonlycreateonewhenwehaveatleastoneentrytoputinit。ThreadLocalMap(ThreadLocallt;?firstKey,ObjectfirstValue){1。初始化entry数组,size:16tablenewEntry〔INITIALCAPACITY〕;2。计算value的indexintifirstKey。threadLocalHashCode(INITIALCAPACITY1);3。在对应index位置赋值table〔i〕newEntry(firstKey,firstValue);4。entrysizesize1;5。设置threshold:thresholdlen23;setThreshold(INITIALCAPACITY);}Settheresizethresholdtomaintainatworsta23loadfactor。privatevoidsetThreshold(intlen){thresholdlen23;}threadLocal。set()方法Setsthecurrentthreadscopyofthisthreadlocalvariabletothespecifiedvalue。Mostsubclasseswillhavenoneedtooverridethismethod,relyingsolelyonthe{linkinitialValue}methodtosetthevaluesofthreadlocals。paramvaluethevaluetobestoredinthecurrentthreadscopyofthisthreadlocal。publicvoidset(Tvalue){1。获取当前线程ThreadtThread。currentThread();2。获取当前线程内部的ThreadLocalMap变量ThreadLocalMapmapgetMap(t);if(map!null)3。设置valuemap。set(this,value);else4。若map为null则创建ThreadLocalMapcreateMap(t,value);}ThreadLocalMap
  从JDK源码可见,ThreadLocalMap中的Entry是弱引用类型的,这就意味着如果这个ThreadLocal只被这个Entry引用,而没有被其他对象强引用时,就会在下一次GC的时候回收掉。staticclassThreadLocalMap{TheentriesinthishashmapextendWeakReference,usingitsmainreffieldasthekey(whichisalwaysaThreadLocalobject)。Notethatnullkeys(i。e。entry。get()null)meanthatthekeyisnolongerreferenced,sotheentrycanbeexpungedfromtable。Suchentriesarereferredtoasstaleentriesinthecodethatfollows。staticclassEntryextendsWeakReferenceThreadLocallt;?{ThevalueassociatedwiththisThreadLocal。Objectvalue;Entry(ThreadLocallt;?k,Objectv){super(k);valuev;}}。。。}ThreadLocal示例鹰眼链路ThreadLocal的使用
  EagleEye(鹰眼)作为全链路监控系统在集团内部被广泛使用,traceId、rpcId、压测标等信息存储在EagleEye的ThreadLocal变量中,并在HSFDubbo服务调用间进行传递。EagleEye通过Filter将数据初始化到ThreadLocal中,部分相关代码如下:EagleEyeHttpRequesteagleEyeHttpRequestthis。convertHttpRequest(httpRequest);1。初始化,将traceId、rpcId等数据存储到鹰眼的ThreadLocal变量中EagleEyeRequestTracer。startTrace(eagleEyeHttpRequest,false);try{chain。doFilter(httpRequest,httpResponse);}finally{2。清理ThreadLocal变量值EagleEyeRequestTracer。endTrace(this。convertHttpResponse(httpResponse));}
  在EagleEyeFilter中,通过EagleEyeRequestTracer。startTrace方法进行初始化,在前置入参转换后,通过startTrace重载方法将鹰眼上下文参数存入ThreadLocal中,相关代码如下:
  EagleEyeFilter在finally代码块中,通过EagleEyeRequestTracer。endTrace方法结束调用链,通过clear方法将ThreadLocal中的数据进行清理,相关代码实现如下:
  Badcase:XX项目权益领取失败问题
  在某权益领取原有链路中,通过app打开一级页面后才能发起权益领取请求,请求经过淘系无线网关(Mtop)后到达服务端,服务端通过mtopsdk获取当前会话信息。
  在XX项目中,对权益领取链路进行了升级改造,在一级页面请求时,通过服务端同时发起权益领取请求。具体地,服务端在处理一级页面请求时,同时通过调用hsfdubbo接口来进行权益领取,因此在发起rpc调用时需要携带用户当前会话信息,在服务提供端将会话信息进行提取并注入到mtop上下文,从而才能通过mtopsdk获取到会话id等信息。某开发同学在实现时,因ThreadLocal使用不当造成下述问题:问题1:因ThreadLocal初始化时机不当,造成获取不到会话信息,进而导致权益领取失败;问题2:请求完成时,因未清理ThreadLocal中的变量值,导致脏数据;
  【问题1:权益领取失败分析】
  在权益领取服务中,该应用构建了一套高效和线程安全的依赖注入框架,基于该框架的业务逻辑模块通常抽象为xxxModule形式,Module间为网状依赖关系,框架会按依赖关系自动调用init方法(其中,被依赖的module的init方法先执行)。
  在应用中,权益领取接口的主入口为CommonXXApplyModule类,CommonXXApplyModule依赖XXSessionModule。当请求来临时,会按依赖关系依次调用init方法,因此XXSessionModule的init方法会优先执行;而开发同学在CommonXXApplyModule类中的init方法中通过调用recoverMtopContext()方法来期望恢复mtop上下文,因recoverMtopContext()方法的调用时机过晚,从而导致XXSessionModule模块获取不到正确的会话id等信息而导致权益领取失败。
  【问题2:脏数据分析】
  权益领取服务在处理请求时,若当前线程曾经处理过权益领取请求,因ThreadLocal变量值未被清理,此时XXSessionModule通过mtopSDK获取会话信息时得到的是前一次请求的会话信息,从而造成脏数据。
  【解决方案】
  在依赖注入框架入口处AbstractGatevisit(或在XXSessionModule中)通过recoverMtopContext方法注入mtop上下文信息,并在入口方法的finally代码块清理当前请求的threadlocal变量值。思考小结ThreadLocalMap中的Entry为什么要设计为弱引用类型?
  若使用强引用类型,则threadlocal的引用链为:ThreadThreadLocal。ThreadLocalMapEntry〔〕Entrykey(threadLocal对象)和value;在这种场景下,只要这个线程还在运行(如线程池场景),若不调用remove方法,则该对象及关联的所有强引用对象都不会被垃圾回收器回收。使用static和不使用static修饰threadlocal变量有和区别?
  若使用static关键字进行修饰,则一个线程仅对应一个线程变量;否则,threadlocal语义变为perThreadperInstance,容易引发内存泄漏,如下述示例:publicclassThreadLocalTest{publicstaticclassThreadLocalDemo{privateThreadLocalStringthreadLocalHoldernewThreadLocal();publicvoidsetValue(Stringvalue){threadLocalHolder。set(value);}publicStringgetValue(){returnthreadLocalHolder。get();}}publicstaticvoidmain(String〔〕args){intcount3;ListThreadLocalDemolistnewLinkedList();for(inti0;icount;i){ThreadLocalDemodemonewThreadLocalDemo();demo。setValue(demoi);list。add(demo);}System。out。println();}}
  在上述main方法第22行debug,可见线程的threadLocals变量中有3个threadlocal实例。在工程实践中,使用threadlocal时通常期望一个线程只有一个threadlocal实例,因此,若不使用static修饰,期望的语义发生了变化,同时易引起内存泄漏。
  最佳实践ThreadLocal变量值初始化和清理建议成对出现
  如果不执行清理操作,则可能会出现:内存泄漏:由于ThreadLocalMap的中key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,从而Entry里面的元素出现null,value的情况。如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,这样可能会导致内存泄露。脏数据:由于线程复用,在用户1请求时,可能保存了业务数据在ThreadLocal中,若不清理,则用户2的请求进来时,可能会读到用户1的数据。
  建议使用try。。。finally进行清理。ThreadLocal变量建议使用static进行修饰
  我们在使用ThreadLocal时,通常期望的语义是perThread,若不使用static进行修饰,则语义变为perThreadperInstance;在线程池场景下,若不用static进行修饰,创建的线程相关实例可能会达到MN个(其中M为线程数,N为对应类的实例数),易造成内存泄漏(https:errorprone。infobugpatternThreadLocalUsage)。谨慎使用ThreadLocal。withInitial
  在应用中,谨慎使用ThreadLocal。withInitial(Supplierlt;?extendsSsupplier)这个工厂方法创建ThreadLocal对象,一旦不同线程的ThreadLocal使用了同一个Supplier对象,那么隔离也就无从谈起了,如:反例,实际上使用了共享对象obj而并未隔离,privatestaticThreadLocalObjthreadLocalThreadLocal。withIntitial(()obj);
  总结
  在java工程实践中,线程池和线程变量被广泛使用,因线程池和线程变量的不当使用经常造成安全生产事故,因此,正确使用线程池和线程变量是每一位开发人员必须修炼的基本功。本文从线程池和线程变量的使用出发,简要介绍了线程池和线程变量的原理和使用实践,各开发人员可结合最佳实践和实际应用场景,正确地使用线程和线程变量,构建出稳定、高效的java应用服务。
  原文链接:https:mp。weixin。qq。comsBdVqvm2wLNv05vMTieevMg

富宁港建设稳步推进富宁港项目正在建设中云南日报记者张文峰摄近日,记者从文山壮族苗族自治州有关部门获悉,珠江航运云南富宁港建设工程项目第一阶段工程自2022年11月启动建设以来,项目管理人员……A股散户已成惊弓之鸟!明日(1月18日)大盘走势分析预判今天大盘高开低走,截止到收盘为止,上证指数下跌0。10。怎么样各位?我又再次准确预判了今日大盘走势,今年以来,已经连续十几个交易日预判准确了!其实我也不想吹嘘自己,但是怎奈成绩……2月5日隆重开启!湾区人才岛咖啡音乐节交通指引来啦!对于很多年轻人来说音乐是通往内心深处的道路而咖啡则能唤醒昏睡的灵魂当咖啡遇到音乐当咖啡音乐节遇上湾区人才岛这一场咖啡音乐节势必震撼!你一定要……超频三蜂鸟华南金牌B66032GB内存和蓝戟DG1独显性能如前言本周我帮公司的同事对旧电脑做了清灰和重装系统,他准备将以前的这台2015年在电脑城组装的旧电脑寄回老家给小孩用。同事趁着要把旧电脑寄回去,准备重新组装个新电脑,这次预……荷兰阵容不整,科曼不该轻易否定范加尔文羊城晚报全媒体记者刘毅荷兰很少输这么惨,3月25日在2024欧洲杯预选赛客场以0比4不敌法国,主帅科曼第二次执教橙衣军团遭遇开门黑。前主帅范加尔在卡塔尔世界杯的带……受蛋荒影响台湾最大连锁火锅店停售蛋品来源:华夏经纬网筑间幸福锅物以新鲜高质量的海鲜、多样化餐点、高CP值且平易近人的消费模式,称霸锅物市场。(图片来源:台湾中时新闻网)据台湾中时新闻网报道吃火锅怎么可……首届全球光电子信息卓越工程师大赛总决赛在光谷举行3月23日,全球光电子信息卓越工程师大赛总决赛在光谷举行,此次大赛以聚光汇谷造就卓越为主题,面向全球光电子信息产业高水平工程师人才,设立揭榜赛拔尖赛创业赛3个赛道,通过比赛推动……消息称宁德时代麒麟电池已量产,可实现整车1000公里续航Tech星球3月21日消息,据澎湃新闻报道,宁德时代的当家王牌技术麒麟电池已经实现量产。据宁德时代当时介绍,麒麟电池的体积利用率突破了72,配用三元电芯能量密度可达255Whk……猎鹰九火箭四小时两连射,日民企月球着陆器完成轨道修正猎鹰9火箭4小时成功执行两次发射任务美国东部时间3月17日下午,一枚8手猎鹰9号火箭从范登堡太空军基地发射52颗星链卫星。当天晚间,一枚6手猎鹰9号火箭从卡纳维拉尔角太空……迷失的中国足球2012年南勇、谢亚龙足协副主席贪污受贿时期黑哨、假球破坏了中国足球形象。时隔十年足协主席陈戌源、主教练李铁耸人听闻贪腐案和一大批足球队员参与其中的假球,中国足球是黑暗;再看看……轻薄本怎么选?大核大显锐龙76800U单挑酷睿i71260P如今轻薄本市场的竞争十分激烈,特别是搭载第12代酷睿P系列和AMD锐龙6000U系列的轻薄本针锋相对,可以说是各有特点,而对具有不同需求的用户来说,在实际的使用过程中,到底孰优……通讯探访东方庞贝城元代集宁路遗址图为元代集宁路遗址。李爱平摄中新网乌兰察布3月23日电题:探访东方庞贝城元代集宁路遗址中新网记者李爱平在整体用铁栏杆包围的元代集宁路古城遗址(简称集宁路遗址)……
63岁郎平大变样,穿国风裙子像少女,被二婚老公宠得像小孩郎平,这个名字在中国体坛上留下了不朽的印记。她作为中国女排的主教练,带领团队取得了无数的荣誉,并被誉为中国女排的教父。然而,她已经退休了,现在的生活已经有了很大的变化,尤其是她……2022年前十大半导体买家名单出炉,苹果以670亿美元支出稳根据调研机构Gartner的数据,2022年全球前十大半导体买家的芯片总支出相较2021年减少约7。6,其中苹果以670亿美元的支出稳居名单第一的位置,而中国大陆厂商联想、步步……快递业最赚钱的一年,顺丰中通圆通申通韵达谁最能赚?来源运联智库(ID:tucmedia)作者贾艺超编辑小L近期,上市快递公司陆续公布2022年度业绩预告。2022年,成为近几年快递行业最赚钱的一年。利润……成熟的处事方式事缓,言迟,人稳沉心静气,把握节奏,终将行稳致远。真正成熟的人,行事缓、说话慢、为人稳。行事宜缓遇到事情,操之过急有时候容易火上浇油;如果放慢节奏,慢慢处理,事情反而可能会得……丁俊晖安全?斯诺克假球事件再生波澜,世界台联中国这3人重罚万众瞩目的斯诺克世界大奖赛正在如火如荼的进行,中国斯诺克一哥丁俊晖4比0横扫宾汉姆成晋级16强,可惜的是8强赛对阵马克威廉姆斯,丁俊晖0比4出局,周跃龙2比4不敌利索夫斯基,中……人到中年易出现哪三种情况?对于人到中年的人来说,一切都很小心,尤其是身体健康方面。哪怕是很小的问题也总是想到去医院解决。当然这个想法也不错,但是我们要更清楚的了解身体,才能更好的预防。1。人到中年……手机价格接连跳水,消费者却反响平平,为何现在人不爱换手机了?手机价格接连跳水,消费者却反响平平,为何现在人不爱换手机了?回想一下,你有多久没换手机了?早在多年前,苹果每次发布新款手机,国内各地区基本上都是加价抢货,发售当天黄……上海打工的新进宝爸宝妈看过了,上海公立幼儿园太难进了!(一)办理流程1。转出申请由家长或监护人向就读幼儿园提出转出申请,就读幼儿园审核转学条件后,对符合转学条件的幼儿,由幼儿园登录浦东新区幼儿园综合信息管理系统开出《浦……869元到泰国,643元到马来西亚出境机票大降价!这类旅游产中新经纬2月8日电(赵佳然)三年了,终于熬出来了!出境团队游的恢复,让旅游市场焕发了新的生机。除旅游产品外,国际航班也在逐步恢复中。近日,多地出境机票价格明显下降,多条到……巴尔韦德世界杯影响了我不能帮助国家队走得更远让我很痛苦直播吧2月8日讯巴尔韦德在赛前新闻发布会上,谈到了自己状态不佳的情况。巴尔韦德表示:世界杯影响了我,当你追求一个目标时,你会有这样的幻想和乐观,你会认为你的国家一切都会顺……来自四年前的俄罗斯世界杯,甜美与狂野的瞬间四年前的俄罗斯世界杯,法国夺得冠军后,FIFA确认俄罗斯共接待了迎来了770万游客,而卡塔尔世界杯前两星期只接待76。5万人游客。相比之下,人们显然还没有完全的恢复下来,……原神夜兰武器圣遗物推荐夜兰武器圣遗物搭配攻略原神夜兰武器怎么搭配?3。4版本更新后夜兰角色登场,玩这个角色的时候武器和圣遗物的选择都很重要,接下来小编要给大家分享的就是夜兰武器圣遗物的搭配方法,各位不清楚的可以查看下面的……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网