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

发布时间:2026/5/20 10:23:13

本地线程ThreadLocal,以及多线程相关问题 在开发过程中我们经常遇到高并发之后使用到多线程,线程池等相关内容,但是我们经常遇到在多个线程里面操作同一个变量出现了高并发数据问题,这种情况其实可以通过本地线程进行避免.publicstaticvoidmain(String[]args){// 创建一个线程池ThreadPoolExecutorexecutornewThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,newSynchronousQueue());// 创建一个本地变量ThreadLocalStringthreadLocalnewThreadLocal();// 本地变量塞入值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){ThreadtThread.currentThread();ThreadLocalMapmapgetMap(t);if(map!null)map.set(this,value);elsecreateMap(t,value);}//之后调用了getMap方法,我们进去看一下,通过当前线程获取到当前本地线程里面的threadLocals属性ThreadLocalMapgetMap(Threadt){returnt.threadLocals;}// 继续往里面跟进,默认值是null// 注意 这里的ThreadLocal.ThreadLocalMap是保存在当前线程对象里面的ThreadLocal.ThreadLocalMapthreadLocalsnull;/* 回到上面的代码返回的ThreadLocalMap返回值是null然后进入else里面的creatMap 可以看到这里创建的ThreadLocalMap里面的key值就是当前new出来的本地线程, 值就是我们set进去的对象 */voidcreateMap(Threadt,TfirstValue){t.threadLocalsnewThreadLocalMap(this,firstValue);}通过翻看源码我们发现,不同线程之间会通过获取 当前线程对象 然后获取 其本地线程属性之后本地线程属性里面保存 key是当前本地线程对象,也就是存在Thread对象里面的属性值:ThreadLocal.ThreadLocalMap值就是之前set进去的值InheritableThreadLocal和ThreadLocal区别最主要的是在父线程的值获取上面ThreadLocal是获取不到父线程里面的设置的值而InheritableThreadLocal可以获取到 我们通过代码看一下publicclassTestThreadLocal{publicstaticvoidmain(String[]args){InheritableThreadLocalStringinheritableThreadLocalnewInheritableThreadLocal();ThreadLocalStringthreadLocalnewThreadLocalString();inheritableThreadLocal.set(555);threadLocal.set(123);doString(inheritableThreadLocal,threadLocal);}publicstaticvoiddoString(InheritableThreadLocalStringinheritableThreadLocal,ThreadLocalStringthreadLocal){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是否还可以在继续使用// 创建线程池设置相关参数ThreadPoolExecutorexecutornewThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,newSynchronousQueue());executor.submit(()-System.out.println(我是你爹));// 线程池关闭executor.shutdown();// 再次使用线程池executor.submit(()-System.out.println(哈哈哈));运行结果:我是你爹 报错.......线程池相关参数corePoolSize:线程池最小线程数量,也就是初始化数量,如果设置了allowCoreThreadTimeOut那么如果线程池里面的线程数量等于核心数量且处于空闲状态那么可以回收,反之不可回收maximumPoolSize:线程池最大线程数量keepAliveTime:线程池里面的线程处于空闲状态多长时间进行回收unit:上述时间的单位workQueue:当线程有任务进来的时候会先进入到队列里面,一般有下面几种队列:1.ArrayBlockingQueue:当任务进来之后会先使用初始化的核心线程数去执行任务,当核心线程都被使用且任务就会进入队列,队列缓存的任务数达到了定义的数量就会创建新的线程去执行任务,但是这里的可以创建新线程的数量就是,最大线程数-核心线程数通过下面的代码看一下publicclassThreadPoolTest{// 定义缓存数量为20的队列staticArrayBlockingQueuearrayBlockingQueuenewArrayBlockingQueue(20);// 定义一个核心线程数是2 最大线程数是5的线程池staticThreadPoolExecutorexecutornewThreadPoolExecutor(2,5,60,TimeUnit.SECONDS,arrayBlockingQueue);publicstaticvoidmain(String[]args){for(inti0;i26;i){System.out.println(当前队列长度是arrayBlockingQueue.size());executor.execute(()-{System.out.println(Thread.currentThread().getName()执行了);try{// 这里让线程睡60秒用来测试一共可以有多少个线程进来 判断缓存队列Thread.currentThread().sleep(60000);}catch(InterruptedExceptione){e.printStackTrace();}});}}}运行结果:可以从运行结果来看刚开始线程1和2执行了队列长度都是0后面队列差浓度慢慢变长,到20后又开始有新的3个线程去执行任务,是因为最大线程数5-核心线程数数23当前队列长度是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.threadNamename;}Overridepublicvoidrun(){try{System.out.println(当前线程threadName正在运行);Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}}}DiscardOldestPolicy:如果核心线程最大线程和队列全部都是满了的状态就会弹出队列第一个任务把新的任务放到任务最后.publicclassThreadPoolTest{publicstaticvoidmain(String[]args){// 定义一个不缓存任务的SynchronousQueue 如果拒绝就弹出第一个任务的线程池ThreadPoolExecutorexecutornewThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue(2),newThreadPoolExecutor.DiscardOldestPolicy());for(inti1;i7;i){System.out.println(添加任务:i);executor.execute(newMyThread(线程i));}IteratorRunnableiteratorexecutor.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){ThreadPoolExecutorexecutornewThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue(2),newThreadPoolExecutor.AbortPolicy());try{for(inti1;i7;i){System.out.println(添加任务:i);executor.execute(newMyThread(线程i));}}catch(Exceptione){e.printStackTrace();}IteratorRunnableiteratorexecutor.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$MyThread65b54208rejected fromjava.util.concurrent.ThreadPoolExecutor1be6f5c3[Running,pool size2,active threads2,queued tasks2,completed tasks0]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正在运行 解释:任务进来先进核心线程,之后进队列,最后是创建到最大的线程(最大线程数量-核心线程)12(2-1)4也就是任务一(核心线程执行)、二和三(进队列)、四(创建出来的新线程执行)任务五和六抛错拒绝执行3.DiscardPolicy跟AbortPolicy区别就是不会抛错4.CallerRunsPolicy如果遇到需要拒绝的任务就抛给主线程执行publicstaticvoidmain(String[]args){ThreadPoolExecutorexecutornewThreadPoolExecutor(1,2,30,TimeUnit.SECONDS,newLinkedBlockingQueue(2),newThreadPoolExecutor.CallerRunsPolicy());try{for(inti1;i7;i){System.out.println(添加任务:i);executor.execute(newMyThread(线程i));}}catch(Exceptione){e.printStackTrace();}IteratorRunnableiteratorexecutor.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才会运行

相关新闻