您的位置:首页 > 编程语言 > Java开发

Java中ThreadLocal的设计与使用

2010-10-15 14:24 537 查看
ThreadLocal

ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,
而是thread local
variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,
就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每
一个线程都完全拥有该变量。线程局部变量并不是Java的新发明,在其它的一些语言编译器实现(如IBM XL
FORTRAN)中,它在语言的层次提供了直接的支持。因为Java中没有提供在语言层次的直接支持,而是提供了一个ThreadLocal的类来提供支
持,所以,在Java中编写线程局部变量的代码相对比较笨拙,这也许是线程局部变量没有在Java中得到很好的普及的一个原因吧。

ThreadLocal
的设计


首先看看ThreadLocal的接口:

Object get() ;

//
返回当前线程的线程局部变量副本 protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值

void
set(Object value);

// 设置当前线程的线程局部变量副本的值

ThreadLocal有3个方法,其中值得注意
的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始
值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行,并且仅执行1次。ThreadLocal中的确实
实现直接返回一个null:

protected Object initialValue() { return null; }

ThreadLocal
是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下
面的示例实现:

public class ThreadLocal

{

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

public Object get()

{

Thread
curThread = Thread.currentThread();

Object o =
values.get(curThread);

if (o == null &&
!values.containsKey(curThread))

{

o = initialValue();

values.put(curThread,
o);

}

return o;

}

public void set(Object newValue)

{

values.put(Thread.currentThread(),
newValue);

}

public Object initialValue()

{

return null;

}

}


然,这并不是一个工业强度的实现,但JDK中的ThreadLocal的实现总体思路也类似于此。

ThreadLocal的使用

如果
希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类
化,比如下面的例子,SerialNum类为每一个类分配一个序号

public class SerialNum

{

//
The next serial number to be assigned

private static int
nextSerialNum = 0;

private static ThreadLocal serialNum = new
ThreadLocal()

{

protected synchronized Object initialValue()

{

return
new Integer(nextSerialNum++);

}

};

public static int get()

{

return
((Integer) (serialNum.get())).intValue();

}

}

SerialNum类的使用将非常
地简单,因为get()方法是static的,所以在需要获取当前线程的序号时,简单地调用:

int serial =
SerialNum.get();

即可。

在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部
变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失效,并等待垃圾收集器收集。

ThreadLocal与其它
同步机制的比较

ThreadLocal和其它同步机制相比有什么优势呢?ThreadLocal和其它所有的同步机制都是为了解决多线程中的对
同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需要很细
致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。
ThreadLocal就从另一个角度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个
线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码
时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象的特定于线程的状态封装进ThreadLocal。

由于
ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal
get当前线程的值是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数的ThreadLocal类将从中受益。也
可以减少强制类型转换,并将一些错误检查提前到了编译期,将一定程度地简化ThreadLocal的使用。

总结

当然
ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有
效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,
如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,
使程序更加易读、简洁。

ThreadLocal 的使用

使用方法一:

Hibernate的文档时看到了关
于使ThreadLocal管理多线程访问的部分。具体代码如下

1. public static final
ThreadLocal session = new ThreadLocal();

2. public static Session
currentSession() {

3. Session s = (Session)session.get();

4. //open
a new session,if this session has none

5. if(s == null){

6. s
= sessionFactory.openSession();

7. session.set(s);

8. }

return
s;

9. }

我们逐行分析

1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法
get()、set()、initialvalue()。

如果不初始化initialvalue,则initialvalue返回
null。

3。session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的
net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了
每个线程都有自己的s(数据库连接)。

5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是
行6。

6。创建一个数据库连接实例 s

7。保存该数据库连接s到ThreadLocal中。

8。如果当前线程已经访问过数
据库了,则从session中get()就可以获取该线程上次获取过的连接实例。

使用方法二

当要给线程初始化一个特殊值
时,需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类化,EasyDBO中创建jdbc
连接上下文就是这样做的:

public class JDBCContext{

private static Logger
logger = Logger.getLogger(JDBCContext.class);

private DataSource ds;

protected
Connection connection;

private boolean isValid = true;

private
static ThreadLocal jdbcContext;

private JDBCContext(DataSource
ds){

this.ds = ds;

createConnection();

}

public
static JDBCContext getJdbcContext(javax.sql.DataSource ds)

{

if(jdbcContext==null)jdbcContext=new
JDBCContextThreadLocal(ds);

JDBCContext context = (JDBCContext)
jdbcContext.get();

if (context == null) {

context = new
JDBCContext(ds);

}

return context;

}

private static
class JDBCContextThreadLocal extends ThreadLocal {

public
javax.sql.DataSource ds;

public
JDBCContextThreadLocal(javax.sql.DataSource ds)

{

this.ds=ds;

}

protected
synchronized Object initialValue() {

return new JDBCContext(ds);

}

}

}

使
用单例模式,不同的线程调用getJdbcContext()获得自己的jdbcContext,都是通过JDBCContextThreadLocal
内置子类来获得JDBCContext对象的线程局部变量,这个变量是该线程所独有的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: