做封面模板下载网站江西省建设监理协会网站

张小明 2026/1/14 22:17:15
做封面模板下载网站,江西省建设监理协会网站,汕头seo关键词,Nginx伪静态WordPress文章目录引言阻塞式中断的哲学线程停止准则强制关闭线程池的弊端基于原子化标识优雅关闭无界队列与毒丸消费用后即焚的线程池利用实现shutdownNow线程中断与取消可监控设计与实现思路功能落地注意事项使用注意事项小结参考引言 我们大部分情况下并发任务都是交由提前设置好的线…文章目录引言阻塞式中断的哲学线程停止准则强制关闭线程池的弊端基于原子化标识优雅关闭无界队列与毒丸消费用后即焚的线程池利用实现shutdownNow线程中断与取消可监控设计与实现思路功能落地注意事项使用注意事项小结参考引言我们大部分情况下并发任务都是交由提前设置好的线程池统一管理这其中对于池化技术的优雅关闭就涉及任务的终止和资源兜底所以本文将针对这一话题展开探讨。我是SharkChiliJava 开发者Java Guide开源项目维护者。欢迎关注我的公众号写代码的SharkChili也欢迎您了解我的开源项目 mini-redishttps://github.com/shark-ctrl/mini-redis。为方便与读者交流现已创建读者群。关注上方公众号获取我的联系方式添加时备注加群即可加入。阻塞式中断的哲学线程停止准则对于线程的生命周期的管理按照并发的哲学除非拥有某个线程否则不能对该线程进行操控所以只有线程所属的线程池才具备对其生命周期的管理所以在Java应用程序的维度它是不具备直接管理线程池线程的权限即非所属线程池维度的线程关闭是需要通过服务于线程池生命周期方法间接关闭线程例如应用程序关闭时对应服务ExecutorService这个服务的关闭可直接通过shutdown或者shutdownNow中断所有的线程//等待所有任务结束后关闭threadPool.shutdown();//即刻关闭所有的任务返回已提交但是还未开始的任务threadPool.shutdownNow();这里也补充一句shutdown和shutdownNow的区别shutdown会等待所有任务执行完成再关闭所以关闭的响应可能会有些许延迟。shutdownNow会直接强行关闭执行任务同时将未启动的任务返回。强制关闭线程池的弊端如果采用强制关闭的方式将线程池直接关闭就可能导致一些资源未能及时处理而丢失例如我们的有一个日志线程它会不断轮询外部线程投递到阻塞队列的信息并将其写入磁盘。如下图可以看到如果日志线程在队列未消费完过程中直接强制打断就会导致一些数据未能及时消费而丢失对应的我们给出这个日志工具的示例可以看到笔者的所编写的Logwritter,它可以通过构造方法日志队列和工作线程写入日志的文件路径logPath使得logThread可以通过阻塞轮询队列完成日志异步写入publicclassLogwritter{privatefinalBlockingQueueStringqueue;privatefinalLogThreadlogThread;//基于外部入参完成消费者线程初始化publicLogwritter(StringlogPath){this.queuenewArrayBlockingQueue(100);this.logThreadnewLogThread(logPath,queue);}//启动消费者线程publicvoidstart(){logThread.start();}//外部线程可通过log方法将消息存入队列中让logThread写入本地publicvoidlog(Stringmsg)throwsInterruptedException{queue.put(msg);}//......}对应的我们也给出日志线程的代码可以看到在interrupted没有被设置为true之前这段该线程就会不断轮询队列数据privatestaticclassLogThreadextendsThread{privatefinalBlockingQueueStringqueue;privatefinalBufferedOutputStreamoutputStream;//基于外部入参初始化日志写入路径和消费日志消息的阻塞队列publicLogThread(StringlogPath,BlockingQueueStringqueue){this.outputStreamFileUtil.getOutputStream(logPath);this.queuequeue;}privatebooleaninterruptedfalse;//中断当前线程publicvoidinterrupt(){this.interruptedtrue;}publicvoidrun(){try{//标识非中断则继续阻塞获取日志数据while(!interrupted){Console.log(阻塞获取日志......);outputStream.write(queue.take().getBytes(StandardCharsets.UTF_8));}}catch(Exceptione){thrownewRuntimeException(e);}finally{if(outputStream!null){try{outputStream.close();}catch(IOExceptione){//......}}}}}对应的我们给出基础调测代码:LogwritterlogwritternewLogwritter(/tmp/logwritter.log);logwritter.start();logwritter.log(hello);logwritter.log(hello);newThread(()-{logwritter.interrupt();}).start();输出结果如下可以看到代码理想情况下会因为标识的设置而中断阻塞获取日志......线程中断......所以这段代码存在一个很严重的缺陷试想一下如果阻塞队列没有元素的情况下我们尝试打断日志线程此时日志线程就会因为阻塞等待队列元素而无法轮询查看中断标识进而处于长时间阻塞等待的一种状态如下代码所示我们不添加任何元素的情况下直接异步打断线程LogwritterlogwritternewLogwritter(/tmp/logwritter.log);logwritter.start();newThread(()-{logwritter.interrupt();}).start();从输出结果就可以看出此时代码就处于一个阻塞状态必须等到获取完一个元素后才能中断循环·阻塞获取日志......基于原子化标识优雅关闭所以要想解决上述问题我们必须做到以下几点中断日志线程时其他线程再次调用log写入日志时会告知日志线程已停止不可进行消息写入中断要尽可能及时日志线程感知避免阻塞等待下一个元素到来时才检查标识完成中断日志线程收到中断信号会确保当前日志写入到文件后再中断停止基于这种思路笔者给出优化后的代码我们先从顶层的Logwritter开始可以看到笔者将中断操作做了如下改动声明一个中断标识和记录阻塞队列容量的变量remaining调用log写入日志前检查是否中断若没中断则累加计数并写入日志到队列反之直接返回中断时设置中断标识并调用logThread中断方法让其中断privatefinalBlockingQueueStringqueue;privatefinalBlockingQueueStringqueue;privatefinalLogThreadlogThread;privatebooleaninterruptedfalse;privateintremaining;//基于外部入参完成消费者线程初始化publicLogwritter(StringlogPath,intsize){this.queuenewArrayBlockingQueue(size);this.logThreadnewLogThread(logPath,queue);}//启动消费者线程publicvoidstart(){logThread.start();}//外部线程可通过log方法将消息存入队列中让logThread写入本地publicvoidlog(Stringmsg)throwsInterruptedException{synchronized(this){//上实例锁检查中断若中断则输出日志直接返回反之累加remainingif(interrupted){Console.log(日志线程已中断消息{}无法写入,msg);return;}remaining;}//将阻塞操作放在锁外部避免因为队列阻塞等待导致所有线程锁住queue.put(msg);Console.log(写入消息成功消息:{},msg);}//中断当前线程publicvoidinterrupt(){synchronized(this){interruptedtrue;}logThread.interrupt();}//......}重点来了LogThread逻辑调整为通过异常感知到中断信号基于interrupted保留中断状态并通过remaining数值完成剩余日志写入privateclassLogThreadextendsThread{privatefinalBlockingQueueStringqueue;privatefinalBufferedOutputStreamoutputStream;//基于外部入参初始化日志写入路径和消费日志消息的阻塞队列publicLogThread(StringlogPath,BlockingQueueStringqueue){this.outputStreamFileUtil.getOutputStream(logPath);this.queuequeue;}publicvoidrun(){try{//线程未中断或者remaining不为0则继续执行循环知道被中断且remaining为0时退出while(!interrupted||remaining!0){try{Stringmsgqueue.take();outputStream.write((msg\r\n).getBytes(StandardCharsets.UTF_8));Console.log(写入日志{}成功,msg);remaining--;}catch(InterruptedExceptione){//收到中断后保存中断状态继续完成队列中元素消费后退出Console.log(线程中断......);interruptedtrue;}}Console.log(线程处理结束);}catch(IOExceptione){//处理io异常}finally{if(outputStream!null){try{outputStream.close();}catch(IOExceptione){//......}}}}}最后我们给出测试代码大体逻辑是启动日志线程后阻塞等待生产者投递日志在日志线程消费者将其打断查看日志线程是否会在收到中断后完成日志消费再退出LogwritterlogwritternewLogwritter(/tmp/logwritter.log,100);//启动日志线程阻塞等待小肥logwritter.start();//写入日志for(inti0;i10;i){logwritter.log(msgi);}//中断newThread(()-logwritter.interrupt()).start();这里笔者基于IDEA的线程模式调试出这段逻辑对应的输出结果如下,可以看到即使收到中断信号线程也会将队列中的消息消费完成再退出循环写入消息成功消息:msg0 写入消息成功消息:msg1 写入消息成功消息:msg2 写入消息成功消息:msg3 写入消息成功消息:msg4 写入消息成功消息:msg5 写入消息成功消息:msg6 写入消息成功消息:msg7 写入消息成功消息:msg8 写入消息成功消息:msg9 写入日志msg0成功 线程中断...... 写入日志msg1成功 写入日志msg2成功 写入日志msg3成功 写入日志msg4成功 写入日志msg5成功 写入日志msg6成功 写入日志msg7成功 写入日志msg8成功 写入日志msg9成功 线程处理结束无界队列与毒丸消费对于传统的生产者消费者模式面对不会阻塞的无界队列我们完全可以使用毒丸(poison pill)即特定的元素作为中断标识确保生产者可以在适当的时机将其放在队列上当消费者消费到这个对象时立即退出对应的我们给出毒丸的定义因为笔者演示的阻塞队列是字符串类型所以协定好的毒丸就是字符串对象//协定好的结束标识publicstaticfinalStringPOISON_PILLPOISON_PILL;消费者的代码逻辑也很简单直接轮询读取队列数据如果碰到的元素是毒丸则直接退出循环publicclassConsumerimplementsRunnable{privatefinalBlockingQueueStringqueue;privatefinalThreadthread;publicConsumer(BlockingQueueStringqueue){this.queuequeue;threadnewThread(this);}publicvoidstart(){Console.log(消费者启动);thread.start();}Overridepublicvoidrun(){while(true){try{//利用毒丸感知异常中断退出Stringelementqueue.take();if(element.equals(POISON_PILL)){Console.log(消费到毒丸消费者立即停止);break;}Console.log(消费元素{}成功,element);}catch(InterruptedExceptione){//......}}}}测试代码如下可以看到笔者尝试插入100w个元素并利用另外一个线程随机插入毒丸将生产者停止BlockingQueueStringqueuenewArrayBlockingQueue(1);ConsumerconsumernewConsumer(queue);consumer.start();newThread(()-{try{//随机插入毒丸ThreadUtil.sleep(RandomUtil.randomInt(5000));queue.put(POISON_PILL);}catch(InterruptedExceptione){//......}}).start();IntStream.range(0,100_0000).forEach(i-{//轮询插入100w个元素try{queue.offer(String.valueOf(i),5,TimeUnit.SECONDS);}catch(InterruptedExceptione){//.....}});可以看到在消费了84w左右的元素时消费者看到毒丸立即停止退出了毒丸在已知的生产者消费者模式下可以注入有限的毒丸标识中断线程所以使用这种方式控制线程就必须保证消费者数目已知可以通过声明有限的毒丸停止线程。用后即焚的线程池对于只需要使用一次注意笔者所强调的只需要使用到一次的异步线程池我们可以直接通过juc流程控制工具CountDownLatch确保线程池中所有任务完成后销毁线程池将线程池限制在当前函数的生命周期。例如我们现在希望执行一批数据的乘2运算我们希望并行执行这批数据的运算再累加起来此时我们就可以遍历这批数据并将其提交到线程池中完成计算并累加然后使用shutdown销毁线程池对应的我们给出一次性线程池示例和演示代码publicstaticvoidmain(String[]args)throwsInterruptedException{Console.log(并发计算和{},calculateInParallel(4));//并发计算和20}/** * 从1开始遍历入参闭集并提交到线程池中执行*2运算累加返回 * * param rangeClosed * return 并行运算的最终结果 */publicstaticintcalculateInParallel(intrangeClosed){LongAdderaddernewLongAdder();CountDownLatchcountDownLatchnewCountDownLatch(rangeClosed);//声明闭集数一致的线程池ExecutorServiceexecutorServiceExecutors.newFixedThreadPool(rangeClosed);for(inti1;irangeClosed;i){//并行运算每个数值的双数倍并利用原子类累加intnumi;executorService.execute(()-{adder.add(num1);countDownLatch.countDown();});}try{//等待所有线程执行完成countDownLatch.await();}catch(InterruptedExceptione){//......}//用后即焚一次性线程池executorService.shutdown();returnadder.intValue();}最后笔者要特别说明一下这个方案有一定的局限性使用时必须保线程池对应的函数仅被使用少次如果单位时间内并发调用这个函数尽可能导致独立线程池飙升进而打爆内存对于此类线程池管理的使用案例感兴趣的读者可以关注笔者这篇文章Java线程池知识点小结https://mp.weixin.qq.com/s/O8MLoni3QE9UA1tLid6C5w利用实现shutdownNow线程中断与取消可监控设计与实现思路从微观的角度了解了关于线程池中的线程的优雅关闭几种技巧之后我们再来聊聊线程池维度对于取消和中断任务的监控。上文我们了解到shutdown是优雅关闭确保所有的任务都执行完成之后销毁线程池。而shutdownNow是一种能够实时关闭正在执行任务同时还能够取消还未执行任务并返回的函数。所以如果对于实时性要求较高的场景我们更推荐使用shutdownNow。但shutdownNow也存在一定的局限性即它只能知晓那些数取消的任务却不知道那些是中断的任务所以shutdownNow对于需要监控异或者恢复中断的任务的场景就有些力不从心了。对此我们可以自行继承线程池框架并对shutdownNow进行改造大体思路为调用shutdown关闭线程池时内部调用shutdownNow获取已提交未执行的任务保存到任务取消列表。shutdownNow会线程池会将正在执行的任务中断利用这个中断判断当前线程池状态是否被设置为关闭且当前线程状态是否中断如果则将其存入中断列表。对应的我们给出落地代码整体实现思路如下自定义线程池继承AbstractExecutorService获取线程池基本行为函数声明取消队列cancelledTaskList和中断队列interruptedTaskList实现stop方法内部调用shutdownNow将已提交未执行的取消任务存入取消队列cancelledTaskListexecute函数重写将外部任务提交到我们内部聚合的线程池中并保证线程池关闭且当前线程执行被中断的情况下将该任务存入中断队列publicclassResumableThreadPoolExecutorextendsAbstractExecutorService{/** * 记录已提交但未执行就被取消的任务 */privatefinalListRunnablecancelledTaskListnewArrayList();/** * 记录正在执行然后被中断的任务 */privatefinalListRunnableinterruptedTaskListnewArrayList();privateExecutorServiceexecutor;publicResumableThreadPoolExecutor(intsize){executorExecutors.newFixedThreadPool(size);}Overridepublicvoidexecute(Runnablecommand){executor.execute(()-{try{Console.log({}执行任务,Thread.currentThread().getName());command.run();}finally{if(isShutdown()Thread.currentThread().isInterrupted()){//将线程池关闭后中断的任务存入中断队列interruptedTaskList.add(command);}}});}publicListRunnablegetCancelledTaskList(){if(!executor.isTerminated()){thrownewRuntimeException(线程池未关闭);}//安全发布取消队列避免对内部取消列表的不安全修改returnnewArrayList(cancelledTaskList);}publicListRunnablegetInterruptedTaskList(){//安全发布取消队列避免对内部中断列表的不安全修改returnnewArrayList(interruptedTaskList);}Overridepublicvoidshutdown(){executor.shutdown();}OverridepublicListRunnableshutdownNow(){returnexecutor.shutdownNow();}publicvoidstop(){cancelledTaskList.addAll(executor.shutdownNow());//help gcexecutornull;}//......}测试代码如下,因为笔者声明的线程池数为1所以关闭线程池之后所得到的中断任务和取消任务数都为1ResumableThreadPoolExecutorthreadPoolnewResumableThreadPoolExecutor(1);threadPool.execute(()-{try{TimeUnit.DAYS.sleep(1);}catch(InterruptedExceptione){Console.log(task-0被中断保留中断状态);//保留中断状态避免catch后中断状态被清除进而导致中断任务无法存入中断队列Thread.currentThread().interrupt();}});threadPool.execute(()-{ThreadUtil.sleep(1,TimeUnit.DAYS);});threadPool.stop();threadPool.awaitTermination(5,TimeUnit.SECONDS);Console.log(中断的任务数{},threadPool.getInterruptedTaskList().size());Console.log(取消的任务数{},threadPool.getCancelledTaskList().size());功能落地注意事项这段代码逻辑比较简单唯一需要注意的是task-0对于终端状态的保留默认情况下shutdown或者shutdownNow关闭线程池时正在执行的线程就会被中断对应的我们可以查看ThreadPoolExecutor的shutdownNow方法印证publicListRunnableshutdownNow(){ListRunnabletasks;finalReentrantLockmainLockthis.mainLock;mainLock.lock();try{//......//中断正在运行的线程interruptWorkers();tasksdrainQueue();}finally{//......}//......returntasks;}重点来了被中断的线程一旦被catch块捕获对应的中断状态就会被清除如果我们不保留这个状态的话那么这个被中断的任务就会b因为状态被清除而导致无法被存入中断队列这也就是为什么笔者的测试代码在捕获到中断之后又手动处理执行一下中断就是为了保证execute的finally语句块能够感知到线程中断状态保证任务能够正确的被存入中断队列更多关于线程中断的管理感兴趣的可以参考笔者这篇文章如何优雅的中断java线程:https://mp.weixin.qq.com/s/GWP9qf5W_O1HJ7UMIzwJvQ使用注意事项该线程虽然保证线程中断与取消状态保留但读者在基于该线程池恢复启动任务时还是需要注意一下任务处理的幂等性因为线程池仅仅保留的中断的状态对于任务的状态并没有做相应的处理。小结我们来简单概括一下本文的内容线程池的几种关闭方式线程池关闭的几个准则和实践一次性线程池的使用技巧如何实现状态可监控的线程池线程池中断与恢复的注意事项我是SharkChiliJava 开发者Java Guide开源项目维护者。欢迎关注我的公众号写代码的SharkChili也欢迎您了解我的开源项目 mini-redishttps://github.com/shark-ctrl/mini-redis。为方便与读者交流现已创建读者群。关注上方公众号获取我的联系方式添加时备注加群即可加入。参考《Java并发编程实战》
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

科技服务网站建设内容电商关键词查询工具

摘要 随着信息技术的快速发展,传统的图书管理系统逐渐暴露出效率低下、扩展性差等问题。前后端分离架构因其灵活性、可维护性和高性能成为现代Web开发的主流模式。图书管理系统作为高校、图书馆及企业知识管理的核心工具,亟需采用现代化的技术框架重构。…

张小明 2025/12/30 8:22:23 网站建设

中国热门网站网络销售怎么跟客户聊天

引言当《Axie Infinity》日活从280万跌至12万,当《StepN》因经济模型崩溃被玩家抛弃,链游行业正经历一场“信任危机”。2024年,全球链游市场规模突破120亿美元,但90%的项目死于“玩法同质化”“经济系统崩盘”“技术性能不足”三大…

张小明 2026/1/11 12:58:39 网站建设

有没有专门做教程的网站wordpress 自定义字段

p5.js网页编辑器终极指南:零基础快速上手创意编程 【免费下载链接】p5.js-editor Deprecated desktop editor for p5.js 项目地址: https://gitcode.com/gh_mirrors/p5/p5.js-editor p5.js网页编辑器是一款专为创意编程设计的可视化开发工具,让编…

张小明 2026/1/9 19:44:58 网站建设

建网站培训机构学做文案的网站

文章系统介绍了AI领域的核心概念,包括泛化、过拟合、欠拟合、梯度、有监督与无监督学习、序列、余弦相似度、词向量和LangChain等术语。以通俗易懂的方式解释这些专业概念,既有理论又有实例,是产品经理和程序员理解大模型原理的实用指南&…

张小明 2026/1/11 4:16:12 网站建设

对电子商务网站建设的感想鞍山信息港二手房

Linux系统文件与目录安全防护及加密签名全攻略 在Linux系统中,保障文件和目录的安全至关重要。这不仅涉及到对重要系统文件的保护,还包括对文件所有权和权限的合理设置,同时加密和签名文件也是确保信息安全的重要手段。下面我们将详细介绍这些方面的内容。 1. 保护文件和目…

张小明 2026/1/2 3:10:12 网站建设

网站建设基本流程图易语言做检测网站更新

从“中文乱码”说起:Keil MDK下载后注释变问号?一文讲透编码问题的本质与实战解决方案你有没有遇到过这样的场景:刚完成Keil MDK下载,兴冲冲打开一个带中文注释的STM32工程,结果代码里的“// 初始化时钟”变成了满屏的…

张小明 2025/12/30 12:30:30 网站建设