ThreadLocal详解

ThreadLocal详解

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

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

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

  首先定义一个ThreadLocal:

复制代码
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();
        if(null == c) tl.set(initConn);
        return tl.get();
    }
    
}
复制代码

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

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

  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
标签:  ThreadLocal
5
0
« 上一篇: ReferenceQueue的使用
» 下一篇: Java局部变量final
posted @  2015-12-09 23:46  神一样的存在 阅读( 41082) 评论( 10编辑  收藏

  
#1楼   2016-12-08 16:57 |  刘荦   
henbucuo

  
#2楼   2016-12-20 15:32 |  xingoo   
你第一段代码,第一次访问难道不会返回null么?

  
#3楼   2017-01-03 11:50 |  王大壮   
觉得是null吧,作者解答一下

  
#4楼 [ 楼主2017-01-03 14:28 |  神一样的存在   
@ 王大壮
改了下哈。

  
#5楼 [ 楼主2017-01-03 14:28 |  神一样的存在   
@ xingoo
改了下哈。

  
#6楼   2017-07-20 15:48 |  顽皮的蝙蝠侠   
这样写的话,每次调用getConn()的时候得到的其实是用一个Connection实例,要是某个线程修改了connection实例,这就没有隔离吧

  
#7楼 [ 楼主2017-07-20 20:06 |  神一样的存在   
@ 顽皮的蝙蝠侠
不会,是线程绑定的,不会存在并发问题

  
#8楼   2017-08-04 12:37 |  Int_Gy   
1
2
3
4
5
6
7
8
private  static  Connection initConn =  null ;
     static  {
         try  {
             initConn = DriverManager.getConnection( "url, name and password" );
         catch  (SQLException e) {
             e.printStackTrace();
         }
     }

1
2
3
4
5
public  Connection getConn() {
        Connection c = tl.get();
        if ( null  == c) tl.set(initConn);
        return  tl.get();
    }

@神一样的存在 
你的这段代码,这里应该会导致每个线程实例自己的ThreadLocals也就是一个ThreadLocalMap实例中存储的key和value都是同一个ThreadLocal实例和同一个Connection实例,也就是代码中的t1和initConn。所以每一个线程都对同一个initConn操作了,只不过这个initConn放在每个线程自己独有的threadLocals这个map集合中。所以是没有隔离了。
这是我的理解。

  
#9楼 [ 楼主2017-08-04 12:42 |  神一样的存在   
@ Int_Gy
threadlocal的其实简单说来就是为了线程绑定公共资源,其目的就是为了是每个线程能够隔离公共资源,防止并发问题

  
#10楼   2017-09-10 14:34 |  Xtrap   
@ 神一样的存在
引用 @Int_Gy
threadlocal的其实简单说来就是为了线程绑定公共资源,其目的就是为了是每个线程能够隔离公共资源,防止并发问题


你的代码确实有问题,没有达到隔离变量的问题。多个线程还是共用一个Connection变量。 
下面是我的测试代码,你可以运行一下。

public class Solution {

private static ThreadLocal<AtomicInteger> tl = new ThreadLocal<AtomicInteger>();
private static AtomicInteger init;
static {
init = new AtomicInteger();
}

public AtomicInteger getConn() {
AtomicInteger c = tl.get();
if(null == c) tl.set(init);
return tl.get();
}


public static void main(String[] args) {

final Solution ss = new Solution();
ss.getConn().set(2);
System.out.println(ss.getConn());

new Thread() {
public void run() {
System.out.println(ss.getConn());
}
}.start();

System.out.println("Hello");
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值