java并发编程 ThreadLocal
一、使用
1.1 不用TheadLocal会有什么问题?
多线程访问共享变量时,由于线程不安全问题,导致num得到的结果是无法确定的。此时如果变量是私有的,就用ThreadLocal,如果是共享的就要加锁。
public class ThreadLocalTest01 { public static Integer num = 0; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { num += 5; System.out.println("num = " + num); }).start(); } } }
1.2 使用ThreadLocal
使得num是线程私有的,跟其他线程隔离
public class ThreadLocalTest02 { public static ThreadLocal<Integer> num = ThreadLocal.withInitial(() -> 0); public static void main(String[] args) { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { threads[i] = new Thread(() -> { Integer localNum = num.get(); localNum += 5; num.set(localNum); System.out.println(Thread.currentThread().getName() + ", num = " + localNum); }, "thread-" + i); } for (Thread thread : threads) { thread.start(); } } }
二、要搞清以下问题
2.1 每个线程的变量副本 是如何存储的?
数组有hash冲突,没有使用链表/红黑树解决,而是用黄金分割(斐波那契散列),能均衡分配到16个槽位上,超过16就扩容
hashmap为什么不用,因为map是用来做存储的
/** * @author * @Description 结果一定是0~15,不重复 * @date 2022/3/2 */ public class Demo { private static final int HASH_INCREMENT = 0x61c88647; public static void magicHash(int size) { int hashCode = 0; for (int i = 0; i < size; i++) { hashCode = i * HASH_INCREMENT + HASH_INCREMENT; System.out.print((hashCode & (size - 1)) + ","); } System.out.println(); } public static void main(String[] args) { magicHash(16); } }
2.2 是ThreadLocal怎么初始化的
2.3 使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
作用:
因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
四、ThreadLocal其他几个注意的点
只要是介绍ThreadLocal的文章都会帮大家认识一个点,那就是内存泄漏问题。我们先来看下面这张图。
/** * -Xms20M -Xmx20M -Xmn10M */ public class ThreadLocalOOM { public static void main(String[] args) { System.out.println("main 线程开始..."); for (int i = 0; i < 100; i++) { ThreadLocal<Byte[]> threadLocal = new ThreadLocal<>(); // 在当前线程存入一个value System.out.println("在线程:"+Thread.currentThread().getName()+" 添加一个threadLocal"); threadLocal.set(new Byte[1024*1024]); // 方法解决,将threadLocal remove threadLocal.remove(); } System.out.println("main 线程完美运行结束"); } }
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。