ThreadLocal 源码解析
了解 ThreadLocal
在多线程中,对于同一个对象的访问和修改会造成冲突,而使用 ThreadLocal 创建的变量只能被当前线程使用,不会受到其他线程的干扰。
例如我们有个计数器,一共有三个线程,我们想让每个线程都从 1 开始计数,然后按顺序 1、2、3、4 递增,这时候用线程同步的话,一种可能的情况是 Thread1-1,Thread2-2,Thread3-3,而我们想要的是 Thread1-1,Thread1-2,Thread1-3,Thread2-1,Thread2-2 这效果。
实现独立计数器
AIncrement.java
class AIncrement { int num = 0; public int increase() { num = num + 1; return num; } public void print() { for (int i = 0; i < 4; i++) { System.out.println(Thread.currentThread().getName() + " " + increase()); } } }
ThreadLocalTest.java
class ThreadLocalTest { public static void main(String[] args) { AIncrement increment = new AIncrement(); for (int i = 0; i < 3; i++) { new Thread(new Runnable() { @Override public void run() { increment.print(); } }).start(); } } }
控制台打印出:
这显然和我们的预期不一样,因为 num
在多线程共享下,读取-赋值-写入的操作引起了冲突。
下面我们用 ThreadLocal
实现我们想要的。BIncrement.java
class BIncrement { ThreadLocal<Integer> num = ThreadLocal.withInitial(() -> 0); public int increase() { num.set(num.get() + 1); return num.get(); } public void print() { for (int i = 0; i < 4; i++) { System.out.println(Thread.currentThread().getName() + " " + increase()); } } }
将测试类稍微改写下,然后运行,控制台打印如下:
这个结果就符合我们的需求了。
ThreadLocalMap
这是因为 ThreadLocal
中含有一个静态内部类 ThreadLocalMap
,该 Map
的键为当前线程,值为我们需要设置的值,也就是说为每一个线程都存储了一个变量。这样子其实就相当于在单个线程中运行代码,自然不会出现多线程中访问变量冲突的问题。
ThreadLocal.withInitial(() -> 0);
是 JDK1.8 新增的方法,相当于
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return super.initialValue(); } };
ThreadLocal API
ThreadLocal
提供的 API
不多,其中 get()
可以获取我们存储的变量值,set()
则是设置变量值,remove()
移除当前线程的变量值,被移除之后,如果再调用 get()
,则会获取初始化的值。
想了解更多关于 ThreadLocal
的内容,可以查看 JDK
源码或者搜索相关资料,这里只是自己做个记录,很浅显。
源码:https://juejin.im/entry/58b048b28ac24728d53abadf#comment
https://juejin.im/entry/58cb76681b69e6006b715c1b
https://juejin.im/entry/597ede3bf265da3e185e7de1