1.ThreadLocal
ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal类,它主要由四个方法组成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()时才执行,并且仅执行1次(即:最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用get()方法访问变量的时候。如果线程先于get方法调用set(T)方法,则不会在线程中再调用initialValue方法)。ThreadLocal中的缺省实现直接返回一个null
在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。ThreadLocal的原理:
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);
}
values.put(Thread.currentThread(), newValue);
return o ;
}
public Object initialValue() {
return null;
}
}
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。
2.ThreadLocal提供的四个方法
protected T initialValue()返回the initial value for this thread-local
public T get()返回the current thread's value of this thread-local
public void set(T value)设置the current thread's copy of this thread-local variable to the specified value. Most subclasses will have no need to override this method, relying solely on the initialValue() method to set the values of thread-locals.
public void remove()Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently read by the current thread, its value will be reinitialized by invoking its initialValue() method, unless its value is set by the current thread in the interim. This may result in multiple invocations of the initialValue method in the current thread.
3.典型示例(应用场景)
1>Hiberante的Session 工具类HibernateUtil
这个类是Hibernate官方文档中HibernateUtil类,用于session管理。
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义SessionFactory
static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 获取当前线程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
2>在微信开发中的应用
缓冲微信开发中的第三方应用,也就是微信公众号.我们的需求是要求每一个公众号在并发执行的过程中能够被共享.一种方法是将该公众号放在缓存中,如当前使用较多的redis缓冲,每次在调用的时候都去redis缓冲中取该公众号,如果发现没有,去数据库中取出来,再将该公众号设置到缓存中.
还可以在拦截器中初始化ThreadLocal的值,或者是在监听器中初始化ThreadLocal的值.
public class AppCache {
private final String KEY_PREFIX = "app:";
private static final ThreadLocal<App> localApp = new ThreadLocal<App>();
@Autowired
private CacheService<App> redisAppCache;
@Autowired
private AppService appService;
public App fetchApp(String appId) {
App app = redisAppCache.get(KEY_PREFIX + appId);
if (app == null) {
app = appService.findById(appId);
if (app != null) {
redisAppCache.set(KEY_PREFIX + appId, app);
} else {
String message = "Can not find the app from the database by id " + appId;
throw new ServiceErrorException(message);
}
}
localAccount.set(app);
return app;
}
//刷新App
public App refreshApp(App app) {
redisAppCache.set(KEY_PREFIX + app.getId(), app);
localApp.set(app);
return app;
}
//删除App,也就是将缓冲中的app给删掉
public void deleteApp(String appId) {
redisAppCache.del(KEY_PREFIX + appId);
localApp.set(null);
}
//在需要该App的时候,我们就可以调用该方法获取
public static App getCurrentApp() {
return localApp.get();
}
}
3>Java的多线程编程中ThreadLocal的应用
可参考百度百科.
4.ThreadLocal和线程同步机制的比较
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。ThreadLocal中可以持有任何类型的对象.
5. 基于JVM的内存模型来分析ThreadLocal
ThreadLocal本质上是一个类似Map的结构,以各个线程对象本身为Key,将其值存放进去。这个结构在所有的基于同一个线程类创建出来的线程中被共享所有,就是只有一个,单例的对象。 虽然,使用get()方法来读取其值,但是默认的是使用当前的thread对象做为Key来检索的。
ThreadLocal存放的内容,不支持基本数据类型,只支持对象类型。用以存放线程私有的数据,以规避繁琐的同步机制下的资源共享。一般而言,少量数据是可以通过这种简便的方式而实现线程访问的。这些数据不涉及到线程之间的通信和共享问题。
6.相关资料查阅
ThreadLocal 百度百科
深入理解ThreadLocal: http://my.oschina.net/clopopo/blog/149368
深入研究ThreadLocal类: http://lavasoft.blog.51cto.com/62575/51926/
理解ThreadLocal: http://blog.jobbole.com/20400/