车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里

朋友们在遇到线程安全问题的时候 , 大多数情况下可能会使用synchronized关键字 , 每次只允许一个线程进入锁定的方法或代码块 , 这样就可以保证操作的原子性 , 保证对公共资源的修改不会出现莫名其妙的问题 。 这种加锁的机制 , 在并发量小的情况下还好 , 如果并发量较大时 , 会有大量的线程等待同一个对象锁 , 会造成系统吞吐量直线下降 。
JDK的开发者可能也考虑到使用synchronized的弊端 , 于是出现了volatile 和 ThreadLocal等另外的思路解决线程安全问题 。 volatile它所修饰的变量不保留拷贝 , 直接访问主内存 , 主要用于一写多读的场景 。 ThreadLocal是给每一个线程都创建变量的副本 , 保证每个线程访问都是自己的副本 , 相互隔离 , 就不会出现线程安全问题 , 这种方式其实用空间换时间的做法 。 其他的内容以后有空再讨论 , 今天我们重点聊一下 ThreadLocal 。
接下来 , 我们将从以下几个方面介绍ThreadLocal

  • 如何使用ThreadLocal?
  • ThreadLocal的工作原理
  • ThreadLocal源码解析
  • ThreadLocal有哪些坑
1.如何使用ThreadLocal?
在使用ThreadLocal之前我们先一起看个例子
/** * 不安全线程场景 * * @author sue * @date 2020/8/12 21:21 */public class TestThread {private int count = 0;public void calc() {count++;}public int getCount() {return count;}public static void main(String[] args) throws InterruptedException {TestThread testThread = new TestThread();for (int i = 0; i < 20; i++) {new ThreadA(i, testThread).start();}Thread.sleep(200);System.out.println("realCount:" + testThread.getCount());}}class ThreadA extends Thread {private int i;private TestThread testThread;ThreadA(int i, TestThread testThread) {this.i = i;this.testThread = testThread;}public void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}testThread.calc();System.out.println("i:" + i + ",count:" + testThread.getCount());}}运行结果:
i:8,count:8i:7,count:8i:11,count:10i:4,count:11i:13,count:12i:2,count:8i:0,count:8i:9,count:8i:3,count:8i:1,count:8i:5,count:8i:6,count:8i:12,count:11i:10,count:9i:14,count:15i:18,count:17i:15,count:18i:17,count:16i:16,count:15i:19,count:18realCount:18我们可以看到 , realCount最终出现错误 , 预计的结果应该是20 , 实际情况却是18 , 出现了线程安全问题 。
接下来 , 把程序改成ThreadLocal运行结果会怎样?
/** * ThreadLocal场景 * * @author sue * @date 2020/8/12 21:21 */public class TestThreadLocal {private ThreadLocal threadLocal = new ThreadLocal<>();public void calc() {threadLocal.set(getCount() + 1);}public int getCount() {Integer integer = threadLocal.get();return integer != null ? integer : 0;}public static void main(String[] args) throws InterruptedException {TestThreadLocal testThreadLocal = new TestThreadLocal();for (int i = 0; i < 20; i++) {new ThreadB(i, testThreadLocal).start();}Thread.sleep(200);System.out.println("realCount:" + testThreadLocal.getCount());}}class ThreadB extends Thread {private int i;private TestThreadLocal testThreadLocal;ThreadB(int i, TestThreadLocal testThreadLocal) {this.i = i;this.testThreadLocal = testThreadLocal;}public void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}testThreadLocal.calc();System.out.println("i:" + i + ",count:" + testThreadLocal.getCount());}}