java的ThreadLocal简介和示例

来源:互联网 发布:三星s8续航优化教程 编辑:程序博客网 时间:2024/05/02 00:25

Java中的ThreadLocal这个类的名字乍看容易让人误解,其实应该叫做ThreadLocalVaraiableKey,这个类的实例是作为key将对应的value存储在当前线程,value才是真正有用的信息。请注意,ThreadLocal对象仅仅是作为线程共享变量map里面的key来使用,其本身并不存储相关信息。一些安全框架如apache shiro和spring security等,都会将登录用户的信息保存到当前线程中,以便在后续的调用中可以调用静态方法获取到,而不是将用户信息作为参数传递到每个被调用的方法中。

下面看一下具体的实现方式和一个例子。

首先java.lang.ThreadThreadLocal.ThreadLocalMap类型的一个成员用以存储当前线程所有的ThreadLocal变量

public class Thread implements Runnable { ... ...  /* ThreadLocal values pertaining to this thread. This map is maintained  * by the ThreadLocal class. */     ThreadLocal.ThreadLocalMap threadLocals = null; ... ... }


        值得注意的是,Thread类本身并不提供方法来获取ThreadLocal变量,ThreadLocal对应的变量值的获取需要通过ThreaLocal对象本身的get方法。
        以apache shiro为例,SecurityUtils.getSubject().getPrincipal()能获取当前线程的principal(其实往往就是login user),注意SecurityUtils.getSubject()是静态方法。下面看一下具体的调用栈
        
        ThreadContext的resources是一个ThreadLocal的变量,其本身就是一个map。getSubject最终就是获取resources这个map里面字符串常量SUBJECT_KEY对应的value。可以看到resources是final的,其hash code不会更改,因此可以作为map的key使用。

下面看一下ThreadLocal的实现,如何将ThreadLocal对象作为key通过get方法获取到对应value。

public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t); // 获取Thread中所有的ThreadLocal变量的map    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);//以调用get方法的对象this为参数,获取map中对应的value        if (e != null)            return (T)e.value;    }    return setInitialValue();}

map.getEntry(this);就是以ThreadLocal对象为key获取对应的value。

  下面的示例代码是从网上看到的,虽然有点绕,但能够帮助理解ThreadLocal。刚开始看以为输出是乱序的1-9这九个数字。

public class SequenceNumber{ // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 private final static ThreadLocal<Integer>    seqNumVar    = new ThreadLocal<Integer>()                                                         {                                                             public Integer initialValue()                                                             {                                                                 return 0;                                                             }                                                         }; // ②获取下一个序列值 public int getNextNum() {     seqNumVar.set(seqNumVar.get() + 1);     return seqNumVar.get(); } public static void main(String[] args) {     SequenceNumber sn = new SequenceNumber();     // ③ 3个线程共享sn,各自产生序列号     TestClient t1 = new TestClient(sn);     TestClient t2 = new TestClient(sn);     TestClient t3 = new TestClient(sn);     t1.start();     t2.start();     t3.start(); } private static class TestClient extends Thread {     private SequenceNumber    sn;     public TestClient(SequenceNumber sn)     {         this.sn = sn;     }     public void run()     {         for (int i = 0; i < 3; i++)         {// ④每个线程打出3个序列值             System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + sn.getNextNum() + "]");         }     } }}


        各个线程的输出次序不考虑的话,输出结果可能是
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
       也就是3个线程的序列分别从1开始,seqNumVar作为三个线程共用的ThreadLocal,序列值互不影响。原因可以简单描述如下:每个线程输出时,都调用ThreadLocal变量seqNumVar的get方法,这时当前线程的ThreadLocal变量map中并没有seqNumVar作key的value,因此在map中添加seqNumVar-0这组key-value,并返回初始值0。

0 0
原创粉丝点击