多线程系列:TheadLocal

来源:互联网 发布:淘宝退货会影响权重吗 编辑:程序博客网 时间:2024/06/05 21:58

本文属于并发编程网多线程学习笔记系列。原文地址:http://ifeve.com/java-theadlocal/

以下为原文:

Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。

常用方法:

1创建一个ThreadLocal变量:

private ThreadLocal myThreadLocal = new ThreadLocal();

你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。

2访问ThreadLocal变量:

一旦创建了一个ThreadLocal对象,你就可以通过以下方式来存储此对象的值:

1myThreadLocal.set("A thread local value");

也可以直接读取一个ThreadLocal对象的值:

1String threadLocalValue = (String) myThreadLocal.get();

get()方法会返回一个Object对象,而set()方法则依赖一个Object对象参数。

3.ThreadLocal泛型

为了使get()方法返回值不用做强制类型转换,通常可以创建一个泛型化的ThreadLocal对象。以下就是一个泛型化的ThreadLocal示例:

private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();

现在你可以存储一个字符串到ThreadLocal实例里,此外,当你从此ThreadLocal实例中获取值的时候,就不必要做强制类型转换。

myThreadLocal1.set("Hello ThreadLocal");String threadLocalValues = myThreadLocal.get();

4.初始化ThreadLocal

我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。如下所示:

private ThreadLocal myThreadLocal = new ThreadLocal<String>() {   @Override protected String initialValue() {       return "This is the initial value";   }};
此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。

demo:


上面的例子创建了一个MyRunnable实例,并将该实例作为参数传递给两个线程。两个线程分别执行run()方法,并且都在ThreadLocal实例上保存了不同的值。如果它们访问的不是ThreadLocal对象并且调用的set()方法被同步了,则第二个线程会覆盖掉第一个线程设置的值。但是,由于它们访问的是一个ThreadLocal对象,因此这两个线程都无法看到对方保存的值。也就是说,它们存取的是两个不同的值。

****************************分割线,原文结束,学习笔记开始********************************************************

源码分析

好了,原文结束,我们来看下背后的源码是如何为每个线程创建一个变量的副本的。

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();    }

逻辑如下:一开始是取得当前线程,然后通过getMap(t)方法获取到一个ThreadLocalMap类型的map。

判断map是否为空,不为空则传入this获取到<key,value>键值对,继而获取value。

如果map为空,则调用setInitialValue方法返回value。

深入进去看下ThreadLocal具体方法实现:

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

就是返回当前线程的threadLocals,再看看thread类的threadLocals是啥,猜测是ThreadLocalMap

    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;
跟猜测的一样,是ThreadLocal的内部类ThreadLocalMap,看下它的实现<img src="http://img.blog.csdn.net/20160412103648467?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /><span style="font-size:18px;">可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。</span>

<p style="font-family: monospace; white-space: pre;"><span style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; line-height: 28px;">关于弱引用,下面在介绍set方法时一并说明。</span><span style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; line-height: 28px;">再看看set具体方法实现:</span></p>

<pre name="code" class="java">    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

实现相对简单:获取当前线程。获取map,判断map不为空,就设置键值对,为空,再创建Map。

其中createmap的实现如下:

    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }
接下来看看ThreadLocalMap与弱引用的关系。

那么WeakReference有什么作用?原谅我这菜鸟水平,之前没有使用过。网上搜了下,是跟垃圾回收有关如果一个对象只有WeakReference引用它,那么这个对象就可能被垃圾回收器回收。

们通常用的都是强引用,比如hashmap,这意味着即使作为key的对象已经不存在了(指没有任何一个引用指向它),也仍然会保留在HashMap中,在某些情况下(例如内存缓存)中,这些过期的条目可能会造成内存泄漏等问题。(这块我先这样理解,应该单独整理下测试下,比如hashmap与weakhashmap的对比。)

我们使用的只是new了一个ThreadLocal对象,所以当用户定义的ThreadLocal对象不再使用之后,ThreadLocal对象及其指向的T对象都应该可以被回收。再回到ThreadLocalMap上面,上面的源码看到到这里的Entry类的k被做了弱引用,所以ThreadLocal对象的回收不会受到entry类的影响,结合set方法看看

 private void set(ThreadLocal key, Object value) {            // We don't use a fast path as with get() because it is at            // least as common to use set() to create new entries as            // it is to replace existing ones, in which case, a fast            // path would fail more often than not.            Entry[] tab = table;            int len = tab.length;            int i = key.threadLocalHashCode & (len-1);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                ThreadLocal k = e.get();                if (k == key) {                    e.value = value;                    return;                }                if (k == null) {                    replaceStaleEntry(key, value, i);                    return;                }            }            tab[i] = new Entry(key, value);            int sz = ++size;            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

遍历table,如果k==null,说明这条记录可以删除,就用新值替换旧的,以便回收内存空间。

总结一下:

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量.

  3)在进行get之前,必须先set,否则会报空指针异常;

      如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

******************************************

应用场景

ThreadLocal的官方API解释为:

“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”

ThreadLocalMap并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果。

http://www.iteye.com/topic/103804lujh99 写道

总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点: 
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
可以这样理解:ThreadLocalMap跟线程安全不是一会事,绑定上去的实例也不是多线程公用的,而是每个线程new一份,这个实例肯定不是共用的,如果共用了,那就会引发线程安全问题,要考其他手段来解决。

大神还举了session的例子:

下面来看一个hibernate中典型的ThreadLocal的应用: 

Java代码  收藏代码
  1. private static final ThreadLocal threadSession = new ThreadLocal();  
  2.   
  3. public static Session getSession() throws InfrastructureException {  
  4.     Session s = (Session) threadSession.get();  
  5.     try {  
  6.         if (s == null) {  
  7.             s = getSessionFactory().openSession();  
  8.             threadSession.set(s);  
  9.         }  
  10.     } catch (HibernateException ex) {  
  11.         throw new InfrastructureException(ex);  
  12.     }  
  13.     return s;  
  14. }  


0 0
原创粉丝点击