ThreadLocal浅析

来源:互联网 发布:iphone数据恢复教程 编辑:程序博客网 时间:2024/05/16 10:43

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。


ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 1.5 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

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