ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal的接口方法
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
set方法中,获取当前线程的实例,然后调用getmap方法,拿到thread里面的ThreadLocalMap(一个map变量,key为threadlocal实例,value为存储的数据副本),如果map为null,则创建一个map
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
=========================
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
看以下示例:
1. 封装threadlocal,为每个线程存储一个Integer数据;注意线程计算结束之后,清空缓存变量,避免线程复用的时候,取到线程上一次计算的变量
public class FishBucketCache {
private static ThreadLocal<Integer> cache = new ThreadLocal<>();
public static Integer get(){
return cache.get();
}
public static void put(Integer var){
cache.set(var);
}
/**
* 删除当前线程的缓存变量,避免线程复用的情况下,取到线程上一次计算的变量
*/
public static void remove(){
cache.remove();
}
}
2. 模拟小猫钓鱼,鱼桶中的鱼计数行为,每钓到一条鱼,扔进桶中,技术
public class CatFishModler implements Runnable {
private String name;
public CatFishModler(String name){
this.name = name;
}
@Override
public void run() {
try{
System.out.println(String.format("time=%s, %s start to fishing...", new Date(), this.name));
ThreadLocalRandom random = ThreadLocalRandom.current();
int count = random.nextInt(0, 10);
do{
if(FishBucketCache.get() == null){
FishBucketCache.put(0);
}else{
FishBucketCache.put(FishBucketCache.get() + 1);
System.out.println(String.format("time=%s, %s get fish +1 = %s", new Date(), this.name, FishBucketCache.get()));
}
count--;
try {
Thread.currentThread().sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}while (count>=0);
System.out.println(String.format("time=%s, %s fish end = %s", new Date(), this.name, FishBucketCache.get()));
}finally {
//清空当前线程当次计算的缓存变量
FishBucketCache.remove();
}
}
}
3. 开3个线程并发测试,但是同一时刻最多允许2个线程执行(用于模拟线程复用)
public class ThreadLocalTest {
public static void main(String[] args){
CatFishModler catFishModler1 = new CatFishModler("CAT1");
CatFishModler catFishModler2 = new CatFishModler("CAT2");
CatFishModler catFishModler3 = new CatFishModler("CAT3");
ExecutorService executorService = Executors.newFixedThreadPool(2);//仅允许2个线程同时执行
executorService.execute(catFishModler1);
executorService.execute(catFishModler2);
executorService.execute(catFishModler3);
}
}
结果:
time=Mon Jul 31 16:20:02 CST 2017, CAT2 start to fishing...
time=Mon Jul 31 16:20:02 CST 2017, CAT1 start to fishing...
time=Mon Jul 31 16:20:02 CST 2017, CAT2 get fish +1 = 1
time=Mon Jul 31 16:20:02 CST 2017, CAT1 get fish +1 = 1
time=Mon Jul 31 16:20:03 CST 2017, CAT1 get fish +1 = 2
time=Mon Jul 31 16:20:03 CST 2017, CAT2 get fish +1 = 2
time=Mon Jul 31 16:20:03 CST 2017, CAT2 get fish +1 = 3
time=Mon Jul 31 16:20:03 CST 2017, CAT1 get fish +1 = 3
time=Mon Jul 31 16:20:04 CST 2017, CAT1 get fish +1 = 4
time=Mon Jul 31 16:20:04 CST 2017, CAT2 get fish +1 = 4
time=Mon Jul 31 16:20:04 CST 2017, CAT2 fish end = 4
time=Mon Jul 31 16:20:04 CST 2017, CAT1 get fish +1 = 5
time=Mon Jul 31 16:20:04 CST 2017, CAT3 start to fishing...
time=Mon Jul 31 16:20:05 CST 2017, CAT1 get fish +1 = 6
time=Mon Jul 31 16:20:05 CST 2017, CAT3 get fish +1 = 1
time=Mon Jul 31 16:20:05 CST 2017, CAT1 fish end = 6
time=Mon Jul 31 16:20:05 CST 2017, CAT3 get fish +1 = 2
time=Mon Jul 31 16:20:06 CST 2017, CAT3 fish end = 2