[021]java中ThreadLocal原理及其使用

2017-04-04  本文已影响0人  shawnxjf

背景

我们都知道ThreadLocal可以解决线程安全问题,它会把共享变量copy一份副本到线程空间中。那么它是怎么做到的,我们又应该如何使用它呢?

Thread Local 是什么

本地变量副本:

它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

Thread Local背后的实现机制-原理

1.每个线程都有一个ThreadLocalMap变量,所以可以想到每个线程独立的变量都是通过ThreadLocalMap数据结构来维护的。

//Thread.java 中
public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
    /**
    * 1.获取本地线程 thread.currentThread();
    * 2.取得本地线程的threadLocalMap,然后往这个map里塞值。
    */
    public void set(T value) {
        Thread t = Thread.currentThread(); //从这里可以看出代码执行这里,就会往当前线程设置变量。
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // ThreadLocalMap(ThreadLocal,Object),以ThreadLocal作为key,object作为value。
            map.set(this, value);
        else
            createMap(t, value);
    }

     /**
     *
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

如何使用Thread Local

了解了ThreadLocal的使用原理,我们先看ThreadLocal类有哪些方法。
initValue()方法
set()方法
get()方法

ThreadLocal{
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//this为ThreadLocal对象
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

我们定义如下threadLocal变量。
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();
private ThreadLocal myThreadLocal2 = new ThreadLocal<String>();
在实际使用过程中
myThreadLocal1.get(),获取的是当前线程中threadLocalMap 里myThreadLocal1为key对应的值。
当调用myThreadLocal1.set("abc"),自动的把abc设置到当前Thread.ThreadLocalMap()中去了,参考ThreadLocal类型set()方法。

注意我们在Thread中添加的ThreadLocal.ThreadLocalMap 变量在线程结束的时候必须释放掉,请查看Thread类exits方法:

 /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

threadLocal 与thread的同步锁比较

threadLocal 是为每个线程维护一份本地的变量,所以是“空间换时间”的方式。
thread同步锁 访问串行化、对象共享化,是“时间换空间”的方式。

如何正确使用threadLocal

由于threadLocal就是解决多个线程之间互相干扰的情况,每个线程都有他们自己的threadLocalMap数据结构。
所以各个线程的threadLocalMap不能指向同一个引用,即每个线程需要肚子引用记录自己的对象threadLocalMap.set(this,new Object())这样线程之间才不会干扰。

/**
 * 由于是thread中有一个threadLocal.threadlocalMap引用,所以
 * @author shawn
 *
 */
public class SafeThreadLocalThread  implements Runnable{
    
    private ThreadLocal myThreadLocal = new ThreadLocal<Number>();
    
    public static  int i = 0;
    
    @Override
    public void run() {
        Number number = new Number();//每个线程需要使用自己的对象。
        number.setNo(i++);
        
        //将值存储到threadLocal中
        myThreadLocal.set(number);
        
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("number.getNo()" + number.getNo());
    }
    
    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new SafeThreadLocalThread());
        }
    }
    
    /**
     * 每个number 各不影响因为他是在各个线程里单独new的。
     * number.getNo()2
     *number.getNo()3
     *number.getNo()1
     *number.getNo()4
     *number.getNo()0
     */
}

详细代码请查看:https://github.com/shawnxjf1/J2seCodeExample

不同级别的缓存

1.threadLocal相当于线程级别的缓存,每个线程绑定一个map容器在这个容器里存储该线程里的数据。
2.而我们在代码中经常用到static 变量的缓存,比如如下代码:

class demo {
 public static Map<String,Object> demoCache= new Hashmap();//定义jvm级别的缓存
}

3.对于tomcat servlet container,j2ee container 每个容器都有自己的classloader,比如每个tomcat每个应用都有自己的appclassLoader只加载自己的class.
比如如下应用app1,app2可能需要缓存的数据是不一样的,我们希望操作app1 的demoCache时候不影响应用app2的。
此时我们需要的缓存级别就是按容器级别(每个容器的classloader不一样),最常用到的容器级别的工具是:

org.apache.commons.beanutils.ContextClassLoaderLocal{
    private final Map<ClassLoader, T> valueByClassLoader = new WeakHashMap<ClassLoader, T>();
}

在apache beanUtils组建注册convert的时候都是放在per(thread) context classloader级别的缓存里。
当然如果上述代码运行在非容器(意味着只有一个classLoader),其效果就相当于static 变量缓存了。
相关代码见参考列 [beanutils注册convert]。

写完后想法

以前使用ThreadLocal 都是从网上摘抄代码片段,但是当我弄明白ThreadLocal原理自己就能够灵活使用ThreadLocal代码来。弄明白了原理后你就是从脑子出发来表达和实现ThreadLocal信息,而如果你只是copy网上代码说明信息是没有经过脑子的,当然我们不要求所有的技术都弄得很深但是你使用它的时候一定要知道它与其他模块或代码对接的原理。

参考

参考1:beanUtils 注册convert

//ContextClassLoaderLocal 类似于threadLocal
BeanUtilsBean{
      private static final ContextClassLoaderLocal<BeanUtilsBean>
            BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>() {
                        // Creates the default instance used when the context classloader is unavailable
                        @Override
                        protected BeanUtilsBean initialValue() {
                            return new BeanUtilsBean();
                        }
                    };

    /**
     * 获取当前classloader的BeanutilsBean
     * Gets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @return The (pseudo-singleton) BeanUtils bean instance
     */
    public static BeanUtilsBean getInstance() {
        return BEANS_BY_CLASSLOADER.get();
    }

    /**
     * Sets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @param newInstance The (pseudo-singleton) BeanUtils bean instance
     */
    public static void setInstance(final BeanUtilsBean newInstance) {
        BEANS_BY_CLASSLOADER.set(newInstance);
    }
}

ConvertUtilsBean{
   protected static ConvertUtilsBean getInstance() {
        return BeanUtilsBean.getInstance().getConvertUtils();
    }
}
上一篇下一篇

猜你喜欢

热点阅读