java的ThreadLocal简介和示例

Java中的ThreadLocal这个类的名字乍看容易让人误解,其实应该叫做ThreadLocalVariableKey,这个类的实例是作为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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值