下面我们看一个例子:
public class ThreadLocalTest1 {
private static Index num = new Index();
// 创建一个Index类型的本地变量
private static ThreadLocal local = new ThreadLocal() {
@Override
protected Index initialValue() {
return num; // 注意这里,返回的是一个已经定义好的对象num,而不是new Index() --> 返回的不是副本
}
};
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
// 取出当前线程的本地变量,并累加5次
Index index = local.get();
for (int i = 0; i < 5; i++) {
index.increase();
}
// 重新设置累加后的本地变量
local.set(index);
System.out.println(Thread.currentThread().getName() + " : " + index.num);
}
}, "Thread-" + i);
}
for (Thread thread : threads) {
thread.start();
}
}
static class Index {
int num;
public void increase() {
num++;
}
}
}
运行结果如下:
Thread-0 : 5
Thread-1 : 10
Thread-3 : 15
Thread-2 : 20
Thread-4 : 25
我们会发现,每一次运行的结果都不一样,为什么会出现这样的情况?
上面的代码中,我们通过覆盖initialValue函数来给我们的ThreadLocal提供初始值,每个线程都会获取这个初始值的一个副本,而现在我们的初始值是一个定义好的对象,num是这个对象的引用。换句话说我们的初始值是一个引用,线程的引用副本指向的是同一个对象。如下图:
把方法改造一下:创建对象的副本而不是对象应用的副本
// 创建一个Index类型的本地变量
private static ThreadLocal local = new ThreadLocal() {
@Override
protected Index initialValue() {
return new Index(); // 注意这里
}
};
来看另外一个例子:
public class ThreadLocalTest2 {
// 创建一个Integer型的线程本地变量
public static final ThreadLocal local = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
// 获取当前线程的本地变量,然后累加5次
int num = local.get();
for (int i = 0; i < 5; i++) {
num++;
}
// 重新设置累加后的本地变量
local.set(num);
System.out.println(Thread.currentThread().getName() + " : " + local.get());
}
}, "Thread-" + i);
}
for (Thread thread : threads) {
thread.start();
}
}
}
运行结果如下:
Thread-4 : 5
Thread-1 : 5
Thread-2 : 5
Thread-0 : 5
Thread-3 : 5
我们可以看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。
ThreadLocal从代码看其实很简单,每个线程调用set方法时,相当于往内部的Map中增加一条记录,key是各自的线程,value是各自线程调用set放进去的值。
public void set(Object value) 设置当前线程的线程局部变量的值;
public Object get() 返回当前线程所对应的线程局部变量;
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。 按照我们自己也可以实现一个简单的ThreadLocal:
public class SimpleThreadLocal {
private Map map = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
// 键为线程对象,值为本线程的变量副本
map.put(Thread.currentThread(), newValue);
}
public Object get() {
Thread currentThread = Thread.currentThread();
// 返回本线程对应的变量
Object obj = map.get(currentThread);
// 如果在Map中不存在,放到Map中保存起来
if (obj == null && !map.containsKey(currentThread)) {
obj = initialValue();
map.put(currentThread, obj);
}
return obj;
}
public void remove() {
map.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
1. hibernate中使用:
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
// 定义SessionFactory
private static final SessionFactory sessionFactory;
// 创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal local = new ThreadLocal();
static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
/**
* 获取当前线程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session session = (Session) local.get();
// 如果Session还没有打开,则新开一个Session
if (session == null) {
session = sessionFactory.openSession();
local.set(session); // 将新开的Session保存到线程局部变量中
}
return session;
}
public static void closeSession() throws HibernateException {
// 获取线程局部变量,并强制转换为Session类型
Session session = (Session) local.get();
local.set(null);
if (session != null)
session.close();
}
}
2. 事务控制中使用:
将会另写博文记录: http://blog.csdn.net/zdp072/article/details/39214867