首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,通常状况下,经过ThreadLocal.set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的。各个线程中访问的是不一样的对象。
另外,说ThreadLocal使得各线程可以保持各自独立的一个对象,并非经过ThreadLocal.set()来实现的,而是经过每一个线程中的new 对象 的操做来建立的对象,每一个线程建立一个,不是什么对象的拷贝或副本。经过ThreadLocal.set()将这个新建立的对象的引用保存到各线程的本身的一个map中,每一个线程都有这样一个map,执行ThreadLocal.get()时,各线程从本身的map中取出放进去的对象,所以取出来的是各自本身线程中的对象,ThreadLocal实例是做为map的key来使用的。
若是ThreadLocal.set()进去的东西原本就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题。
下面来看一个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);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
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);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
能够看到在getSession()方法中,首先判断当前线程中有没有放进去session,若是尚未,那么经过sessionFactory().openSession()来建立一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的惟一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession做为这个值的key,要取得这个session能够经过threadSession.get()来获得,里面执行的操做实际是先取得当前线程中的ThreadLocalMap,而后将threadSession做为key将对应的值取出。这个session至关于线程的私有变量,而不是public的。
显然,其余线程中是取不到这个session的,他们也只能取到本身的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。
试想若是不用ThreadLocal怎么来实现呢?可能就要在action中建立session,而后把session一个个传到service和dao中,这可够麻烦的。或者能够本身定义一个静态的map,将当前thread做为key,建立的session做为值,put到map中,应该也行,这也是通常人的想法,但事实上,ThreadLocal的实现恰好相反,它是在每一个线程中有一个map,而将ThreadLocal实例做为key,这样每一个map中的项数不多,并且当线程销毁时相应的东西也一块儿销毁了,不知道除了这些还有什么其余的好处。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。概括了两点:
1。每一个线程中都有一个本身的ThreadLocalMap类对象,能够将线程本身的对象保持到其中,各管各的,线程能够正确的访问到本身的对象。
2。将一个共用的ThreadLocal静态实例做为key,将不一样对象的引用保存到不一样线程的ThreadLocalMap中,而后在线程执行的各处经过这个静态ThreadLocal实例的get()方法取得本身线程保存的那个对象,避免了将这个对象做为参数传递的麻烦。
固然若是要把原本线程共享的对象经过ThreadLocal.set()放到线程中也能够,能够实现避免参数传递的访问方式,可是要注意get()到的是那同一个共享对象,并发访问问题要靠其余手段来解决。但通常来讲线程共享的对象经过设置为某类的静态变量就能够实现方便的访问了,彷佛不必放到线程中。
ThreadLocal的应用场合,我以为最适合的是按线程多实例(每一个线程对应一个实例)的对象的访问,而且这个对象不少地方都要用到。
下面来看看ThreadLocal的实现原理(jdk1.5源码)
- public class ThreadLocal<T> {
- /**
- * ThreadLocals rely on per-thread hash maps attached to each thread
- * (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
- * objects act as keys, searched via threadLocalHashCode. This is a
- * custom hash code (useful only within ThreadLocalMaps) that eliminates
- * collisions in the common case where consecutively constructed
- * ThreadLocals are used by the same threads, while remaining well-behaved
- * in less common cases.
- */
- private final int threadLocalHashCode = nextHashCode();
-
- /**
- * The next hash code to be given out. Accessed only by like-named method.
- */
- private static int nextHashCode = 0;
-
- /**
- * The difference between successively generated hash codes - turns
- * implicit sequential thread-local IDs into near-optimally spread
- * multiplicative hash values for power-of-two-sized tables.
- */
- private static final int HASH_INCREMENT = 0x61c88647;
-
- /**
- * Compute the next hash code. The static synchronization used here
- * should not be a performance bottleneck. When ThreadLocals are
- * generated in different threads at a fast enough rate to regularly
- * contend on this lock, memory contention is by far a more serious
- * problem than lock contention.
- */
- private static synchronized int nextHashCode() {
- int h = nextHashCode;
- nextHashCode = h + HASH_INCREMENT;
- return h;
- }
-
- /**
- * Creates a thread local variable.
- */
- public ThreadLocal() {
- }
-
- /**
- * Returns the value in the current thread's copy of this thread-local
- * variable. Creates and initializes the copy if this is the first time
- * the thread has called this method.
- *
- * @return the current thread's value of this thread-local
- */
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- return (T)map.get(this);
-
- // Maps are constructed lazily. if the map for this thread
- // doesn't exist, create it, with this ThreadLocal and its
- // initial value as its only entry.
- T value = initialValue();
- createMap(t, value);
- return value;
- }
-
- /**
- * Sets the current thread's copy of this thread-local variable
- * to the specified value. Many applications will have no need for
- * this functionality, relying solely on the {@link #initialValue}
- * method to set the values of thread-locals.
- *
- * @param value the value to be stored in the current threads' copy of
- * this thread-local.
- */
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
-
- /**
- * Get the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
- *
- * @param t the current thread
- * @return the map
- */
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-
- /**
- * Create the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
- *
- * @param t the current thread
- * @param firstValue value for the initial entry of the map
- * @param map the map to store.
- */
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
-
- .......
-
- /**
- * ThreadLocalMap is a customized hash map suitable only for
- * maintaining thread local values. No operations are exported
- * outside of the ThreadLocal class. The class is package private to
- * allow declaration of fields in class Thread. To help deal with
- * very large and long-lived usages, the hash table entries use
- * WeakReferences for keys. However, since reference queues are not
- * used, stale entries are guaranteed to be removed only when
- * the table starts running out of space.
- */
- static class ThreadLocalMap {
-
- ........
-
- }
-
- }
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread hash maps attached to each thread
* (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
* objects act as keys, searched via threadLocalHashCode. This is a
* custom hash code (useful only within ThreadLocalMaps) that eliminates
* collisions in the common case where consecutively constructed
* ThreadLocals are used by the same threads, while remaining well-behaved
* in less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Accessed only by like-named method.
*/
private static int nextHashCode = 0;
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Compute the next hash code. The static synchronization used here
* should not be a performance bottleneck. When ThreadLocals are
* generated in different threads at a fast enough rate to regularly
* contend on this lock, memory contention is by far a more serious
* problem than lock contention.
*/
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
/**
* Returns the value in the current thread's copy of this thread-local
* variable. Creates and initializes the copy if this is the first time
* the thread has called this method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Many applications will have no need for
* this functionality, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current threads' copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
.......
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
........
}
}
能够看到ThreadLocal类中的变量只有这3个int型:
- private final int threadLocalHashCode = nextHashCode();
- private static int nextHashCode = 0;
- private static final int HASH_INCREMENT = 0x61c88647;
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode = 0;
private static final int HASH_INCREMENT = 0x61c88647;
而做为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。
能够来看一下建立一个ThreadLocal实例即new ThreadLocal()时作了哪些操做,从上面看到构造函数ThreadLocal()里什么操做都没有,惟一的操做是这句:
- private final int threadLocalHashCode = nextHashCode();
private final int threadLocalHashCode = nextHashCode();
那么nextHashCode()作了什么呢:
- private static synchronized int nextHashCode() {
- int h = nextHashCode;
- nextHashCode = h + HASH_INCREMENT;
- return h;
- }
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,而后nextHashCode的值增长HASH_INCREMENT这个值。
所以ThreadLocal实例的变量只有这个threadLocalHashCode,并且是final的,用来区分不一样的ThreadLocal实例,ThreadLocal类主要是做为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?
看一下上面的set()方法,两句合并一下成为
- ThreadLocalMap map = Thread.currentThread().threadLocals;
ThreadLocalMap map = Thread.currentThread().threadLocals;
这个ThreadLocalMap 类是ThreadLocal中定义的内部类,可是它的实例却用在Thread类中:
- public class Thread implements Runnable {
- ......
-
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ......
- }
public class Thread implements Runnable {
......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
......
}
再看这句:
- if (map != null)
- map.set(this, value);
if (map != null)
map.set(this, value);
也就是将该ThreadLocal实例做为key,要保持的对象做为值,设置到当前线程的ThreadLocalMap 中,get()方法一样你们看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,本身去看源码吧。 java
深刻研究java.lang.ThreadLocal类
1、概述
ThreadLocal是什么呢?其实ThreadLocal并不是是一个线程的本地实现版本,它并非一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用很是简单,就是为每个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每个线程均可以独立地改变本身的副本,而不会和其它线程的副本冲突。
从线程的角度看,每一个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的而且 ThreadLocal 实例是可访问的;在线程消失以后,其线程局部实例的全部副本都会被垃圾回收(除非存在对这些副本的其余引用)。
经过ThreadLocal存取的数据,老是与当前线程相关,也就是说,JVM 为每一个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何作到为每个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每个线程的变量的副本。
归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。
2、API说明
ThreadLocal()
建立一个线程本地变量。
T get()
返回此线程局部变量的当前线程副本中的值,若是这是线程第一次调用该方法,则建立并初始化此副本。
protected T initialValue()
返回此线程局部变量的当前线程的初始值。最多在每次访问线程来得到每一个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。若是线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
若该实现只返回 null;若是程序员但愿将线程局部变量初始化为 null 之外的某个值,则必须为 ThreadLocal 建立子类,并重写此方法。一般,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
void remove()
移除此线程局部变量的值。这可能有助于减小线程局部变量的存储需求。若是再次访问此线程局部变量,那么在默认状况下它将拥有其 initialValue。
void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不须要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
在程序中通常都重写initialValue方法,以给定一个特定的初始值。
3、典型实例
一、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();
}
}
在这个类中,因为没有重写ThreadLocal的initialValue()方法,则首次建立线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。所以,对session作了判断,若是为null,则新开一个Session,并保存到线程局部变量session中,这一步很是的关键,这也是“public static final ThreadLocal session = new ThreadLocal()”所建立对象session能强制转换为Hibernate Session对象的缘由。
二、另一个实例
建立一个Bean,经过不一样的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:45:02
* 学生
*/
public class Student {
private int age = 0; //年龄
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:53:33
* 多线程下测试程序
*/
public class ThreadLocalDemo implements Runnable {
//建立线程局部变量studentLocal,在后面你会发现用来保存Student对象
private final static ThreadLocal studentLocal = new ThreadLocal();
public static void main(String[] agrs) {
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
public void run() {
accessStudent();
}
/**
* 示例业务方法,用来测试
*/
public void accessStudent() {
//获取当前线程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
//产生一个随机数并打印
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
//获取一个Student对象,并将随机数年龄插入到对象属性中
Student student = getStudent();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(500);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
protected Student getStudent() {
//获取本地线程变量并强制转换为Student类型
Student student = (Student) studentLocal.get();
//线程首次执行此方法的时候,studentLocal.get()确定为null
if (student == null) {
//建立一个Student对象,并保存到本地线程变量studentLocal中
student = new Student();
studentLocal.set(student);
}
return student;
}
}
运行结果:
a is running!
thread a set age to:76
b is running!
thread b set age to:27
thread a first read age is:76
thread b first read age is:27
thread a second read age is:76
thread b second read age is:27
能够看到a、b两个线程age在不一样时刻打印的值是彻底相同的。这个程序经过妙用ThreadLocal,既实现多线程并发,游兼顾数据的安全性。
4、总结
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每一个线程的中并发访问的数据提供一个副本,经过访问副原本运行业务,这样的结果是耗费了内存,单大大减小了线程同步所带来性能消耗,也减小了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。可是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每个线程都提供了变量的副本,使得每一个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通讯时可以得到数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
固然ThreadLocal并不能替代synchronized,它们处理不一样的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
5、ThreadLocal使用的通常步骤
一、在多线程的类(如ThreadDemo类)中,建立一个ThreadLocal对象threadXxx,用来保存线程间须要隔离处理的对象xxx。
二、在ThreadDemo类中,建立一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
三、在ThreadDemo类的run()方法中,经过getXxx()方法获取要操做的数据,这样能够保证每一个线程对应一个数据对象,在任什么时候刻都操做的是这个对象。
ThreadLocal是什么 程序员
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类能够很简洁地编写出优美的多线程程序。 sql
ThreadLocal很容易让人望文生义,想固然地认为是一个“本地线程”。其实,ThreadLocal并非一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。 安全
当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。 session
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。 多线程
线程局部变量并非Java的新发明,不少语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地经过ThreadLocal的类提供支持。 并发
因此,在Java中编写线程局部变量的代码相对来讲要笨拙一些,所以形成线程局部变量没有在Java开发者中获得很好的普及。 app
ThreadLocal的接口方法 less
ThreadLocal类接口很简单,只有4个方法,咱们先来了解一下: dom
设置当前线程的线程局部变量的值。
该方法返回当前线程所对应的线程局部变量。
将当前线程局部变量的值删除,目的是为了减小内存的占用,该方法是JDK 5.0新增的方法。须要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,因此显式调用该方法清除线程的局部变量并非必须的操做,但它能够加快内存回收的速度。
- 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是如何作到为每个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。咱们本身就能够提供一个简单的实现版本:
代码清单1 SimpleThreadLocal
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
package com.baobaotao.basic;
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和线程同步机制相比有什么优点呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析何时对变量进行读写,何时须要锁定某个对象,何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另外一个角度来解决多线程的并发访问。ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每个线程都拥有本身的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,能够把不安全的变量封装进ThreadLocal。
因为ThreadLocal中能够持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,须要强制类型转换。但JDK 5.0经过泛型很好的解决了这个问题,在必定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。
归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。
Spring使用ThreadLocal解决线程安全问题
咱们知道在通常状况下,只有无状态的Bean才能够在多线程环境下共享,在Spring中,绝大部分Bean均可以声明为singleton做用域。就是由于Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,由于有状态的Bean就能够在多线程中共享了。
通常的Web应用划分为展示层、服务层和持久层三个层次,在不一样的层中编写对应的逻辑,下层经过接口向上层开放功能调用。在通常状况下,从接收请求到返回响应所通过的全部程序调用都同属于一个线程,如图9‑2所示:
图1同一线程贯通三层
这样你就能够根据须要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,全部关联的对象引用到的都是同一个变量。
下面的实例可以体现Spring对有状态Bean的改造思路:
代码清单3 TopicDao:非线程安全
public class TopicDao {
private Connection conn;①一个非线程安全的变量
public void addTopic(){
Statement stat = conn.createStatement();②引用非线程安全变量
…
}
}
因为①处的conn是成员变量,由于addTopic()方法是非线程安全的,必须在使用时建立一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:
代码清单4 TopicDao:线程安全
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共享了。
固然,这个例子自己很粗糙,将Connection的ThreadLocal直接放在DAO只能作到本DAO的多个方法共享Connection时不发生线程安全问题,但没法和其它DAO共用同一个Connection,要作到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。
小结
ThreadLocal是解决线程安全问题一个很好的思路,它经过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在不少状况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。