之前各种加锁保证并发场景下的变量操作的安全性,但是在某些场景下,可以给线程分配其私有变量,在每个线程绑定其私有变量,通过get()和set()方法获取或者修改当前线程私有的变量值,从而避免了线程安全问题
1. 使用 ThreadLocal 类
import java.util.Random;
class House{
// 全部线程的总共销售额
int sale=0;
// 加锁保证线程共有变量的正确性
public synchronized void saleHouse(){sale++;}
// 每个线程自己的销售额
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(()->0);
public void saleCountByThreadLocal(){saleVolume.set(saleVolume.get()+1);}
}
public class ThreadLocalDemo {
public static void main(String[] args) throws InterruptedException {
// 模拟10个销售员卖房子
House house = new House();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
int random = new Random().nextInt(5)+1;
try {
for (int j = 1; j <= random; j++) {
house.saleHouse();
house.saleCountByThreadLocal();
}
System.out.println("线程"+Thread.currentThread().getName()+" "+"销售额="+house.saleVolume.get());
}finally {
// 线程私有变量使用完成之后一定要释放掉---在线程池线程复用的情况下,线程私有变量会导致bug
house.saleVolume.remove();
}
},String.valueOf(i)).start();
}
Thread.sleep(300);
System.out.println("总销售额 = "+house.sale);
}
}
输出:
//线程8 销售额=3
//线程6 销售额=1
//线程1 销售额=4
//线程3 销售额=5
//线程2 销售额=2
//线程4 销售额=5
//线程9 销售额=2
//线程5 销售额=4
//线程7 销售额=4
//线程10 销售额=3
//总销售额 = 33
2. ThreadLocal 源码解析
set()方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
可以看到方法里面都是先通过 Thread.currentThread() 方法获得当前执行该方法的线程,然后通过ThreadLocalMap 找到该线程内的属性值 threadLocals,这个threadLocals是一个map,里面保存的是<当前的ThreadLocal实例, value>的key-value键值对
Thread -> ThreadLocalMap(threadLocals) -> 存储的是<this,value>
get()方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}