`

ThreadLocal

阅读更多

转自:http://hi.baidu.com/cjjic02/blog/item/1ba41813aabde8886438dbe5.html

 

ThreadLocal是Thread的局部变量。

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

 

 

 

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

  • 设置当前线程的线程局部变量的值:
void set(Object value) 
  •  返回当前线程所对应的线程局部变量:
public Object get() 
  •  将当前线程局部变量的值删除:
public void remove() 

 

  • 返回该线程局部变量的初始值,这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
protected Object initialValue() 

 

 

 

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

public class SimpleThreadLocal {

	private Map valueMap = Collections.synchronizedMap(new HashMap());

	public void set(Object newValue) {

		valueMap.put(Thread.currentThread(), newValue);// ①键为线程对象,值为本线程的变量副本

	}

	public Object get() {

		Thread currentThread = Thread.currentThread();

		Object o = valueMap.get(currentThread);// ②返回本线程对应的变量

		if (o == null && !valueMap.containsKey(currentThread)) {// ③如果在Map中不存在,放到Map中保存起来。

			o = initialValue();

			valueMap.put(currentThread, o);

		}

		return o;

	}

	public void remove() {

		valueMap.remove(Thread.currentThread());

	}

	public Object initialValue() {

		return null;

	}

}

 


虽然代码清单9‑3这个ThreadLocal实现版本显得比较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是相近的。

 

 

 

 

一个TheadLocal实例:

下面,我们通过一个具体的实例了解一下ThreadLocal的具体使用方法。

代码清单2 SequenceNumber

public class SequenceNumber {

	//①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
	private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
		public Integer initialValue(){
			return 0;
		}
	};

	//②获取下一个序列值

	public int getNextNum(){
		seqNum.set(seqNum.get()+1);
		return seqNum.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()+"]");

			}

		}

	}

}

 

通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:

thread[Thread-2] sn[1]

thread[Thread-0] sn[1]

thread[Thread-1] sn[1]

thread[Thread-2] sn[2]

thread[Thread-0] sn[2]

thread[Thread-1] sn[2]

thread[Thread-2] sn[3]

thread[Thread-0] sn[3]

thread[Thread-1] sn[3]

考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

 

 



Thread同步机制的比较:

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

 

 

 

Spring使用ThreadLocal解决线程安全问题:

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因此有状态的Bean就可以在多线程中共享了。

 

 

下面的实例能够体现Spring对有状态Bean的改造思路:

TopicDao:非线程安全

public class TopicDao {

private Connection conn;一个非线程安全的变量

public void addTopic(){

Statement stat = conn.createStatement();引用非线程安全变量

}

}

由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

 

import java.sql.Connection;

import java.sql.Statement;

 

public class TopicDao {

//①使用ThreadLocal保存Connection变量

private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

public static Connection getConnection(){

 

//②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,

并将其保存到线程本地变量中。

if (connThreadLocal.get() == null) {

Connection conn = ConnectionManager.getConnection();

connThreadLocal.set(conn);

return conn;

}else{

return connThreadLocal.get();  //③直接返回线程本地变量

}

}

 

public void addTopic() {

//④从ThreadLocal中获取线程对应的Connection

Statement stat = getConnection().createStatement();

}

}

 

 

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

 

小结:

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。








































参考:http://www.builder.com.cn/2007/0529/404695.shtml

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics