当前位置: 首页 > news >正文

本地线程ThreadLocal,以及多线程相关问题

在开发过程中我们经常遇到高并发之后使用到多线程,线程池等相关内容,但是我们经常遇到在多个线程里面操作同一个变量出现了高并发数据问题,这种情况其实可以通过本地线程进行避免.

publicstaticvoidmain(String[]args){// 创建一个线程池ThreadPoolExecutorexecutor=newThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,newSynchronousQueue<>());// 创建一个本地变量ThreadLocal<String>threadLocal=newThreadLocal<>();// 本地变量塞入值threadLocal.set("张三");// 使用线程池运行在里面获取本地线程的内容executor.submit(newRunnable(){@Overridepublicvoidrun(){System.out.println("当前是线程池"+threadLocal.get());}});// new出来一个线程获取本地线程的内容newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("当前是new出来的线程"+threadLocal.get());}}).start();System.out.println("当前是主线程"+Thread.currentThread().getName()+threadLocal.get());}}// 运行结果:当前是线程池null当前是主线程main张三 当前是new出来的线程null
  • 不难发现本地线程存入数据就真的只是当前所在线程可以获取到其他线程都是获取不到的

本地线程存的值是放在哪里的 怎么区分不同本地线程里面的数据,我们通过查看源码来探索一下

// 我们创建一个本地线程 往里面set值的时候下面的代码就是源码,首先获取到当前线程publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);}//之后调用了getMap方法,我们进去看一下,通过当前线程获取到当前本地线程里面的threadLocals属性ThreadLocalMapgetMap(Threadt){returnt.threadLocals;}// 继续往里面跟进,默认值是null// 注意 这里的ThreadLocal.ThreadLocalMap是保存在当前线程对象里面的ThreadLocal.ThreadLocalMapthreadLocals=null;/* 回到上面的代码返回的ThreadLocalMap返回值是null然后进入else里面的creatMap 可以看到这里创建的ThreadLocalMap里面的key值就是当前new出来的本地线程, 值就是我们set进去的对象 */voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}通过翻看源码我们发现,不同线程之间会通过获取 当前线程对象 然后获取 其本地线程属性之后本地线程属性里面保存 key是当前本地线程对象,也就是存在Thread对象里面的属性值:ThreadLocal.ThreadLocalMap值就是之前set进去的值

InheritableThreadLocal和ThreadLocal区别最主要的是在父线程的值获取上面
ThreadLocal是获取不到父线程里面的设置的值而InheritableThreadLocal可以获取到 我们通过代码看一下

publicclassTestThreadLocal{publicstaticvoidmain(String[]args){InheritableThreadLocal<String>inheritableThreadLocal=newInheritableThreadLocal<>();ThreadLocal<String>threadLocal=newThreadLocal<String>();inheritableThreadLocal.set("555");threadLocal.set("123");doString(inheritableThreadLocal,threadLocal);}publicstaticvoiddoString(InheritableThreadLocal<String>inheritableThreadLocal,ThreadLocal<String>threadLocal){newThread(()->{System.out.println(Thread.currentThread().getName()+":inheritableThreadLocal的值是:"+inheritableThreadLocal.get());System.out.println(Thread.currentThread().getName()+":threadLocal的值是:"+threadLocal.get());// 在创建一个子线程查看是否能获取到newThread(()->System.out.println(Thread.currentThread().getName()+"inheritableThreadLocal的值是的值是:"+inheritableThreadLocal.get())).start();}).start();}}结果:Thread-0:inheritableThreadLocal的值是:555Thread-0:threadLocal的值是:nullThread-1inheritableThreadLocal的值是的值是:555可以看到InheritableThreadLoacl就算嵌套线程也是可以获取到父线程 里面设置的值的,但是ThreadLocal就不行了

线程池shutdown是否还可以在继续使用

// 创建线程池设置相关参数ThreadPoolExecutorexecutor=newThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,newSynchronousQueue<>());executor.submit(()->System.out.println("我是你爹"));// 线程池关闭executor.shutdown();// 再次使用线程池executor.submit(()->System.out.println("哈哈哈"));运行结果:我是你爹 报错.......

线程池相关参数

  1. corePoolSize:线程池最小线程数量,也就是初始化数量,如果设置了allowCoreThreadTimeOut那么如果线程池里面的线程数量等于核心数量且处于空闲状态那么可以回收,反之不可回收
  2. maximumPoolSize:线程池最大线程数量
  3. keepAliveTime:线程池里面的线程处于空闲状态多长时间进行回收
  4. unit:上述时间的单位
  5. workQueue:当线程有任务进来的时候会先进入到队列里面,一般有下面几种队列:
    1.ArrayBlockingQueue:当任务进来之后会先使用初始化的核心线程数去执行任务,当核心线程都被使用且任务就会进入队列,队列缓存的任务数达到了定义的数量就会创建新的线程去执行任务,但是这里的可以创建新线程的数量就是,最大线程数-核心线程数通过下面的代码看一下
publicclassThreadPoolTest{// 定义缓存数量为20的队列staticArrayBlockingQueuearrayBlockingQueue=newArrayBlockingQueue(20);// 定义一个核心线程数是2 最大线程数是5的线程池staticThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,60,TimeUnit.SECONDS,arrayBlockingQueue);publicstaticvoidmain(String[]args){for(inti=0;i<26;i++){System.out.println("当前队列长度是"+arrayBlockingQueue.size());executor.execute(()->{System.out.println(Thread.currentThread().getName()+"执行了");try{// 这里让线程睡60秒用来测试一共可以有多少个线程进来 判断缓存队列Thread.currentThread().sleep(60000);}catch(InterruptedExceptione){e.printStackTrace();}});}}}运行结果:可以从运行结果来看刚开始线程12执行了队列长度都是0后面队列差浓度慢慢变长,20后又开始有新的3个线程去执行任务,是因为最大线程数5-核心线程数数2=3当前队列长度是0当前队列长度是0当前队列长度是0当前队列长度是1pool-1-thread-1执行了 pool-1-thread-2执行了 当前队列长度是2当前队列长度是3当前队列长度是4当前队列长度是5当前队列长度是6当前队列长度是7当前队列长度是8当前队列长度是9当前队列长度是10当前队列长度是11当前队列长度是12当前队列长度是13当前队列长度是14当前队列长度是15当前队列长度是16当前队列长度是17当前队列长度是18当前队列长度是19当前队列长度是20当前队列长度是20当前队列长度是20当前队列长度是20pool-1-thread-3执行了 pool-1-thread-4执行了 pool-1-thread-5执行了

2.LinkedBlockingQuene:按照笔者的理解这是一个长度为Integer.MAX长度的队列可以指定长度,只要有任务就塞进去,之后线程池里面只要有空闲的线程就来这里面拿任务执行,如果核心线程全部有任务在执行那么就会进入任务队列
3.SynchronousQuene:这个队列和ArrayBlockingQueue最大的区别就是数组阻塞队列是可以缓存任务的,就是刚才所说的线程里面的任务队列,而SynchronousQuene每个线程没有任务队列,如果有任务进来就立马去线程池拿线程执行任务,没线程就创建,当池里面的数量大于最大线程数量就拒绝执行

拒绝策略:

// 创建一个线程任务publicclassMyThreadimplementsRunnable{privateStringthreadName;publicMyThread(Stringname){this.threadName=name;}@Overridepublicvoidrun(){try{System.out.println("当前线程"+threadName+"正在运行");Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}}}
  1. DiscardOldestPolicy:如果核心线程最大线程和队列全部都是满了的状态就会弹出队列第一个任务把新的任务放到任务最后.
publicclassThreadPoolTest{publicstaticvoidmain(String[]args){// 定义一个不缓存任务的SynchronousQueue 如果拒绝就弹出第一个任务的线程池ThreadPoolExecutorexecutor=newThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue<>(2),newThreadPoolExecutor.DiscardOldestPolicy());for(inti=1;i<7;i++){System.out.println("添加任务:"+i);executor.execute(newMyThread("线程"+i));}Iterator<Runnable>iterator=executor.getQueue().iterator();while(iterator.hasNext()){MyThreadnext=(MyThread)iterator.next();System.out.println("当前队列还有任务"+next.threadName);}}}结果:添加任务:1添加任务:2添加任务:3添加任务:4添加任务:5当前线程线程1正在运行 添加任务:6当前线程线程4正在运行 当前队列还有任务线程5当前队列还有任务线程6当前线程线程5正在运行 当前线程线程6正在运行 解释:首先代码运行添加6个任务到线程池里面没问题 之后因为我们创建的是一个核心线程两个最大线程和2个长度的队列 所以第一个任务进来就直接执行了,第二个和第三个会进入队列 第四个进来就会去开启最大线程数了所以就会直接拿到线程运行 这时候第五个任务进来由于核心线程、队列、最大线程都是满载状态 就会触发拒绝策略弹出队列里面最早的任务也就是任务二加入任务五 任务六进来也是一样弹走最早的任务三加入任务六.

2.AbortPolicy核心线程队列和最大线程都满载就直接抛出异常拒绝任务

publicstaticvoidmain(String[]args){ThreadPoolExecutorexecutor=newThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue<>(2),newThreadPoolExecutor.AbortPolicy());try{for(inti=1;i<7;i++){System.out.println("添加任务:"+i);executor.execute(newMyThread("线程"+i));}}catch(Exceptione){e.printStackTrace();}Iterator<Runnable>iterator=executor.getQueue().iterator();while(iterator.hasNext()){MyThreadnext=(MyThread)iterator.next();System.out.println("当前队列还有任务"+next.threadName);}}结果:添加任务:1添加任务:2添加任务:3添加任务:4当前线程线程1正在运行 添加任务:5当前线程线程4正在运行java.util.concurrent.RejectedExecutionException:Taskcom.qmpay.threadPool.ThreadPoolTest$MyThread@65b54208rejected fromjava.util.concurrent.ThreadPoolExecutor@1be6f5c3[Running,pool size=2,active threads=2,queued tasks=2,completed tasks=0]atjava.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)atjava.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)atjava.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)atcom.qmpay.threadPool.ThreadPoolTest.main(ThreadPoolTest.java:17)当前队列还有任务线程2当前队列还有任务线程3当前线程线程2正在运行 当前线程线程3正在运行 解释:任务进来先进核心线程,之后进队列,最后是创建到最大的线程(最大线程数量-核心线程)=1+2+(2-1)=4也就是任务一(核心线程执行)、二和三(进队列)、四(创建出来的新线程执行)任务五和六抛错拒绝执行

3.DiscardPolicy跟AbortPolicy区别就是不会抛错
4.CallerRunsPolicy如果遇到需要拒绝的任务就抛给主线程执行

publicstaticvoidmain(String[]args){ThreadPoolExecutorexecutor=newThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue<>(2),newThreadPoolExecutor.CallerRunsPolicy());try{for(inti=1;i<7;i++){System.out.println("添加任务:"+i);executor.execute(newMyThread("线程"+i));}}catch(Exceptione){e.printStackTrace();}Iterator<Runnable>iterator=executor.getQueue().iterator();while(iterator.hasNext()){MyThreadnext=(MyThread)iterator.next();System.out.println("当前队列还有任务"+next.threadName);}}结果:添加任务:1添加任务:2添加任务:3添加任务:4添加任务:5当前线程线程1正在运行 当前线程线程5正在运行 当前线程线程4正在运行 添加任务:6当前线程线程2正在运行 当前线程线程3正在运行 当前队列还有任务线程6当前线程线程6正在运行 解释:任务:一、二、三、四不用解释跟之前一样,任务五进入拒绝策略里面抛给主线程执行 所以任务一 四 五会直接运行,但是这个时候任务五是主线程运行的会发生主线程阻塞 等到主线程运行结束才会继续原代码的执行上述添加任务6才会运行
http://www.jsqmd.com/news/507004/

相关文章:

  • 解决STM32 RTC闹钟不准确问题:HAL库配置与调试技巧
  • 从零搭建Keras-GPU开发环境:避坑指南与一站式配置
  • cv_unet_image-colorization多场景落地解析:家谱修复/博物馆数字化/教育史料还原
  • 别再零散学了!超详细计算机网络基础知识,从入门到精通一篇封神
  • ERNIE-4.5-0.3B-PT实战教程:Chainlit前端支持暗色模式与多语言切换
  • Qwen3-ForcedAligner-0.6B实战案例:跨国团队站会录音→中英双语时间戳字幕同步
  • KEIL5.30编译uCosiii代码时遇到的3个典型报错及解决方案(附详细截图)
  • DAMO-YOLO结合排班脚本:实现员工分时段通行权限控制
  • VUE的solt使用
  • Beyond Language Modeling: An Exploration of Multimodal Pretraining
  • 避坑指南:Postman接口测试中90%人会犯的3个参数配置错误(附正确示范)
  • 全任务零样本学习-mT5中文-base精彩案例:科研基金申请书创新点扩写
  • EagleEye效果增强:检测框+关键点联合输出(如人体姿态辅助判断)
  • helm3 部置traefik2
  • 【通信协议对比】Xmodem、Ymodem、Zmodem、ASCII与Binary的传输效率与适用场景解析
  • 年薪30W+的秘密:网络安全_挖漏洞_必备的4类工具与漏洞复
  • HarmonyOS 6实战:从CustomDialog到Navigation Dialog模式的状态管理升级
  • 3秒获取百度网盘提取码:baidupankey智能工具完全指南
  • 空气发生器怎么选不踩坑?2026口碑榜+选购指南一次讲透 - 品牌推荐大师1
  • 2026年评价高的香氛五金品牌推荐:精油香氛五金/智能香氛五金供应商怎么选 - 行业平台推荐
  • FISCO-BCOS多机构联盟链环境搭建实战指南
  • 2026山东饲料加工降本增效设备5强名单公布,权威数据揭示行业格局 - 精选优质企业推荐榜
  • 2026年感应圈/电炉感应圈/中频炉感应圈/高频炉感应圈/熔炼炉感应圈/淬火炉感应圈/退火感应圈/工频炉感应圈/加热感应圈优选推荐:唐山市丰润区宝军电源设备制造厂 - 2026年企业推荐榜
  • 讲讲特灵空调维修选购,南京地区口碑好的公司有哪些 - 工业推荐榜
  • cJSON的字符长度和字符比较以及数组
  • 如何从Python脚本到可执行文件:微信聊天记录导出工具打包完全指南
  • 从电脑到AI:中国技术命名本土化简史,以及为什么AI到现在还没有中文名
  • 卷积:一种共享参数的“不全连接”
  • PSP汉化手稿 PSP中的动态链接库
  • 2026 电磁流量计十大品牌排行榜:行业头部厂家权威排名 - 品牌推荐大师1