ThreadLocal类

ThreadLocal详解
本文详细介绍了ThreadLocal的概念及其在Java中的具体实现,通过对比同步机制解释了ThreadLocal如何为每个线程提供独立的变量副本,避免了线程安全问题。此外,还通过一个具体的数据库连接管理示例展示了ThreadLocal的应用场景。

本文参考文章:http://www.cnblogs.com/dolphin0520/p/3920407.html  (在其基础上做了总结)

                            http://www.iteye.com/topic/103804

一、ThreadLocal介绍

        ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本。

        ThreadLocal类接口很简单,只有4个方法:

        (1) void set(Object value)设置当前线程的线程局部变量的值。

        (2) public Object get()该方法返回当前线程所对应的线程局部变量。

        (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

        (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调 用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

        值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应 进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

        概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

        先看一个例子:

class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

        假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问 题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变 量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用 closeConnection关闭链接。

        所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。

        这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

        那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个 connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

        到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

class Dao{
    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection();
         
        //使用connection进行操作
         
        connectionManager.closeConnection();
    }
}

        这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压 力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

        那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部 都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

        但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

二、java.lang.ThreadLocal<T>的具体实现

        get()方法的实现:

	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)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

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

        ThreadLocalMap这个类型是ThreadLocal类的一个内部类。

       private T setInitialValue(){
		T value=initialValue();
		Thread t=Thread.currentThread();
		ThreadLocalMap map=getMap(t);
		if(map!=null){
			map.set(this,value);
		}else{
			createMap(t,value);
		}
		return value;
	}
	void createMap(Thread t,T firstValue){
		t.threadLocals=new ThreadLocalMap(this,firstValue);
	}

        ThreadLocal是如何为每个线程创建变量的副本的:

        首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

        初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为 value,存到threadLocals。

        然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

三、ThreadLocal的应用场景

        最常见的ThreadLocal使用场景为用来解决 数据库连接、Session管理等。

        hibernate中典型的ThreadLocal的应用:

	private static final ThreadLocal threadSession=new ThreadLocal();
	
	public static Session getSession() throws InfrastructureException{
		 Session s=(Session)threadSession.get();
		 try{
			 if(s==null){
				 s=getSessionFactory().openSession();
				 threadSession.set(s);
			 }
		 }cache(HibernateException ex){
			 throw new InfrastructureException(ex);
		 }
		 return s;
	}

        试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao 中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是 一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个 map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。(避免了对象作为参数传递的麻烦)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值