首页 >  JAVA频道 > 技术分享 > 

ThreadLocal的用法

ThreadLocal的用法

作者:yjl 来源:华育国际 时间:2015-02-27 访问次数:1626
ThreadLocal的用法,作为解决线程安全的一种方法.ThreadLocalRandom就是一个典型例子 1.代码示例

/**
 *
 * ThreadLocal例子
 *
 * <pre>
 *     1.线程局部变量.其完全不提供锁.以空间换时间的手段,为每个线程提供变量的独立副本,以保证线程的安全.
 *     2.接口:
 *         public T get() 返回此线程局部变量的当前线程副本中的值
 *         public void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值
 *         public void remove() 移除此线程局部变量当前线程的值
 *         protected T initialValue() 返回此线程局部变量的当前线程的“初始值”
 *  3.源码:
 *       // 1.取得当前调用线程t 2.根据t获得ThreadLocalMap【Entry(ThreadLocal k, Object v)】(即每个线程都有一个threadLocals)
 *       // 3. 如果map为不null,则将this和value设置到map;否则创建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);
 *     }
 *    
 *     // 1.取得当前调用线程t 2.取得t线程关联的ThreadLocalMap
 *     // 3.如果map不为null则将this作为key得到Entry.然后得到值.
 *     // 4.如果map为null则返回initialValue(默认为null,同时会将initialValue set至当前线程)
 *     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();
 *     }.
 *  4.注意:
 *      1.不同线程间的对象副本并不是由ThreadLocal创建的.因为set方法未生成任何value的副本.所以将一个对象的实例设置到不同线程的ThreadLocal中,同样无法保证线程安全.
 *  所以需要每个线程内创建副本,然后设置到ThreadLocal中(即ThreadLocal只是一个key而已.).
 *      2.从get方法的最后一句看出,如果没有set,则第一次调用get方法时会调用initialValue;可使用匿名内部类覆写此方法,如果不希望初始值为null.另外如果调用了remove方法,即从
 *  ThreadLocalMap移除ThreadLocal key,则再次调用get方法时依然会调用initialValue.
 *      3.ThreadLocal实例通常是类中的 private static final字段(1.其只是用来做key,和当前线程绑定,所以用static方便 2.通常用静态方法直接获取局部变量实例,
 *  如ThreadLocalRandom),它们希望将状态与某一个线程相关联.
 *
 *  5.总结:
 *      简单来说,ThreadLocal就是一个当前线程局部变量表的一个key,该key对应的V是new出来的一个新值,每个线程的都不一样.这样
 *  当前线程拿到ThreadLocal.get->则直接获得线程当前局部变量表对应的V.(第一次调用get时会将会将initialValue set,
 *  所以初始化ThreadLocal的时候可覆写initialValue).
 * </pre>
 *
 * @author landon
 *
 */
public class ThreadLocalExample {
    // 一个线程局部变量
    private static final ThreadLocal<ThreadLocalVar> localVar = new ThreadLocal<>();
    
    // 线程局部变量内部的V
    private static class ThreadLocalVar {
        public int var;
    }
    
    // 初始化的时候指定了localVar2的变量值
    private static final ThreadLocal<ThreadLocalVar> localVar2 = new ThreadLocal<ThreadLocalVar>() {
        protected ThreadLocalVar initialValue() {
            return new ThreadLocalVar();
        }
    };
    
    // 一个任务
    private static class ThreadLocalTask implements Runnable {
        @Override
        public void run() {
            // 注意这里必须要new一个值,然后set.->即set了当前调用线程中局部变量的值.
            ThreadLocalVar var = new ThreadLocalVar();
            localVar.set(var);
    
            // 随机一个局部变量的值.这里用到了ThreadLocalRandom,一个活生生的例子
    
            // public static ThreadLocalRandom current() {
            // return localRandom.get();
            // }
    
            // 这里初始化了一个线程局部变量值,是new了一个ThreadLocalRandom.这个很关键.
            // private static final ThreadLocal<ThreadLocalRandom> localRandom =
            // new ThreadLocal<ThreadLocalRandom>() {
            // protected ThreadLocalRandom initialValue() {
            // return new ThreadLocalRandom();
            // }
            // };
            var.var = ThreadLocalRandom.current().nextInt();
    
            // 打印当前调用线程,即当前调用线程的线程局部变量的值.
            System.out.println("curThread:" + Thread.currentThread().getName()
                    + " localVar:" + localVar.get().var);
    
            // 操作localVar2,将当前线程的id复制给localvar2
            localVar2.get().var = (int) Thread.currentThread().getId();
    
            System.out.println("curThread:" + Thread.currentThread().getName()
                    + " localVar2:" + localVar2.get().var);
        }
    }
    
    // 从输出看,ThreadLocalExample#localVar如果不是线程局部变量则多线程操作时,一定要加锁的.但是如果是线程局部变量,则其只用来做一个key而已,局部变量的值是new出来
    // 并设置,所以每个线程的ThreadLocal的get值都是互相没有任何关系的。
    // 多线程之间可随意操作localVar.
    // 同理localvar2
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalTask(), "Thread-" + i).start();
        }
    
        // 主线程操作
        localVar.set(new ThreadLocalVar());
        localVar.get().var = 100;
    
        System.out.println("curThread:" + Thread.currentThread().getName()
                + " localVar:" + localVar.get().var);
    
        // 主线程操作localvar2
        localVar2.get().var = 201;
    
        System.out.println("curThread:" + Thread.currentThread().getName()
                + " localVar:" + localVar2.get().var);
    }
}