ThreadLocal详解

    xiaoxiao2021-12-14  16

    ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。

      这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

      我们从源码的角度来分析这个问题。

      首先定义一个ThreadLocal:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  ConnectionUtil {      private  static  ThreadLocal<Connection> tl =  new  ThreadLocal<Connection>();      private  static  Connection initConn =  null ;      static  {          try  {              initConn = DriverManager.getConnection( "url, name and password" );          }  catch  (SQLException e) {              e.printStackTrace();          }      }            public  Connection getConn() {          Connection c = tl.get();          tl.set(initConn);          return  c;      }       }

      这样子,都是用同一个连接,但是每个连接都是新的,是同一个连接的副本。

      那么实现机制是如何的呢?

      1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

    1 2 3 /* ThreadLocal values pertaining to this thread. This map is maintained   * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals =  null ;

      2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

    1 2 3 4 5 6 7 8 9 10 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(); }

      3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

      4、总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。

      5、应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

      6、其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。

      事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。

    如果你定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap。并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义。那么你不要sychronize这么复杂的东西,ThreadLocal将是你不错的选择。

    举例来说:

    import java.util.HashMap;

    public class TreadLocalTest {

        static ThreadLocal<HashMap> map0 = new ThreadLocal<HashMap>(){          @Override          protected HashMap initialValue() {              System.out.println(Thread.currentThread().getName()+"initialValue");              return new HashMap();          }      };      public void run(){          Thread[] runs = new Thread[3];          for(int i=0;i<runs.length;i++){              runs[i]=new Thread(new T1(i));          }          for(int i=0;i<runs.length;i++){              runs[i].start();          }      }      public static class T1 implements Runnable{          int id;          public T1(int id0){              id = id0;          }          public void run() {              System.out.println(Thread.currentThread().getName()+":start");              HashMap map = map0.get();              for(int i=0;i<10;i++){                  map.put(i, i+id*100);                  try{                      Thread.sleep(100);                  }catch(Exception ex){                  }              }              System.out.println(Thread.currentThread().getName()+':'+map);          }      }      /**       * Main       * @param args       */      public static void main(String[] args){          TreadLocalTest test = new TreadLocalTest();          test.run();      }

    }

    输出解释;

    Thread-1:start  Thread-2:start  Thread-0:start  Thread-2initialValue  Thread-1initialValue  Thread-0initialValue  Thread-1:{0=100, 1=101, 2=102, 3=103, 4=104, 5=105, 6=106, 7=107, 8=108, 9=109}  Thread-2:{0=200, 1=201, 2=202, 3=203, 4=204, 5=205, 6=206, 7=207, 8=208, 9=209}  Thread-0:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}

    可以看到map0 虽然是个静态变量,但是initialValue被调用了三次,通过debug发现,initialValue是从map0.get处发起的。而且每个线程都有自己的map,虽然他们同时执行。

    进入Theadlocal代码,可以发现如下的片段;

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

    这说明ThreadLocal确实只有一个变量,但是它内部包含一个map,针对每个thread保留一个entry,如果对应的thread不存在则会调用initialValue。

    原文:http://www.cnblogs.com/dreamroute/p/5034726.html

    转载请注明原文地址: https://ju.6miu.com/read-968806.html

    最新回复(0)