了解 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
https://mp.weixin.qq.com/s/Dsy_fBzrzvbSUtcLuc7ebg
https://mp.weixin.qq.com/s/rZplvCTu31jVX5fwqsGoxg