ThreadLocal中你不得不知道的知识 朱雀 2022-10-16 04:55 200阅读 0赞 **前言**: 一般在项目中,有时候会有大量使用线程的情况,如果是读操作的话不会涉及到线程安全的问题,但如果是写操作那就会考虑到线程本身的安全问题了。 所谓的线程不安全是指多个线程同时去操作同一个全局变量,如果执行的结果和我们所预期想的不一样,那么我们就称之为线程安全问题。 > 在Java中如何解决线程安全问题? > > 1. 使用synchronized或者是Lcok > 2. 使用ThreadLock 如果是使用互斥锁即synchronized或Lock,锁的实现方案是通过线程排队,上个线程结束后且释放锁下个线程才能进入,从而解决线程安全的问题;但是这样做效率肯定会有所下降,以时间换空间的方法在大部分项目中都不可取。 当我们使用ThreadLocal时特别要注意**内存溢出**(Out Of Memory,简称 OOM) > 什么是内存溢出? 是指无用的对象一直占据着内存且没有被释放,并且一直得不到释放,这种无用对象占据内存空间的现象就称为内存溢出。 > 代码演示部分 package com.enu; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * @author:zjy * @description: * @create:2021-05-26 23:16 **/ public class test { //创建一个初始化数组大小 10m static class MyTask{ private byte[] bytes = new byte[10 * 1024 * 1024]; } //自定义一个ThreadLocal private static ThreadLocal<MyTask> threadLocal = new ThreadLocal<>(); //创建主测试方法 public static void main(String[] args) throws InterruptedException { //创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 5, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)); //循环创建10次 for (int i = 0; i < 10; i++) { executeTask(threadPoolExecutor); Thread.sleep(1000); } } private static void executeTask(ThreadPoolExecutor threadPoolExecutor) { threadPoolExecutor.execute(() -> { MyTask myTask = new MyTask(); System.out.println("创建对象"); //将对象设置进ThreadLocal threadLocal.set(myTask); }); } } 这部分代码最终的打印结果: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o1ODMyNDg3NTY_size_16_color_FFFFFF_t_70] 如果我们在idea中设置堆的内存大小(建议根据电脑内存大小决定,尽量设置小一些),然后再执行上部分的代码,那么程序会报错(内存溢出)。 ![在这里插入图片描述][2021052623394097.png] > 原因及分析 为什么我们使用ThreadLocal会报内存溢出的错误? 接下来我们一起进入源码来分析一波。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o1ODMyNDg3NTY_size_16_color_FFFFFF_t_70 1] 当我们使用set方法时: 1. 获取到当前的线程 2. 根据当前线程获取到变量 3. 如果变量不为null则把该变量以k v 的形式存储到map中去 这样我们不难看出每个线程执行时会通过ThreadLocal把变量存储到一个叫ThreadLocalMap的集合中去。 贴示出ThreadLocalMap的源码: static class ThreadLocalMap{ // 实际存储数据的数组 private Entry[] table; // 存储变量的set方法 private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } } } 从上述源码我们可以看出:ThreadMap 中有一个 Entry\[\] 数组用来存储所有的数据,而 Entry 是一个包含 key 和 value 的键值对,其中 key 为 ThreadLocal 本身,而 value 则是要存储在 ThreadLocal 中的值。 因为线程池的生命周期很长,所以线程池存储的数据(value)则会一直存在,从而造成了内存处于紧张或溢出的场景。 **可以通过如下办法避免这样的场景出现,代码如下:** package com.enu; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * @author:zjy * @description: * @create:2021-05-26 23:16 **/ public class test { //创建一个初始化数组大小 10m static class MyTask{ private byte[] bytes = new byte[10 * 1024 * 1024]; } //自定义一个ThreadLocal private static ThreadLocal<MyTask> threadLocal = new ThreadLocal<>(); //创建主测试方法 public static void main(String[] args) throws InterruptedException { //创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 5, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)); //循环创建10次 for (int i = 0; i < 10; i++) { executeTask(threadPoolExecutor); Thread.sleep(1000); } } private static void executeTask(ThreadPoolExecutor threadPoolExecutor) { threadPoolExecutor.execute(() -> { MyTask myTask = new MyTask(); System.out.println("创建一个对象"); try { //将对象设置进ThreadLocal threadLocal.set(myTask); //其他业务代码 }finally { //移除掉该线程在内存中所产生的数据 threadLocal.remove(); } }); } } 通过ThreadLocal中的remove()方法来解决该问题,这么牛皮的方法,我们先通过源码来解析一波再说。 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 通过该代码段可以看出最后会在Thread中移除ThreadLocalMap,也就是说会把该集合中所存储的数据都丢弃掉从而释放内存空间,就不会在使用ThreadLocal的时候造成OOM。 本章完!下次再见 > 文章持续更新中,感兴趣的看官们可以微信搜一搜【爱搞事的程序猿】,欢迎你一起来讨论! [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o1ODMyNDg3NTY_size_16_color_FFFFFF_t_70]: /images/20221014/e17f083172c94a19977cdb0da7d8e01b.png [2021052623394097.png]: /images/20221014/5daee34329bf4564b64f5cbef115b7be.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o1ODMyNDg3NTY_size_16_color_FFFFFF_t_70 1]: /images/20221014/41496e26a9bd4c2a82c809cd2285e52c.png
还没有评论,来说两句吧...