您的位置:首页 > 其它

(精)理解ThreadLocal

2014-05-23 10:31 225 查看

ThreadLocal是什么

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
设置当前线程的线程局部变量的值。
public Object get()
该方法返回当前线程所对应的线程局部变量。
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是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同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

源码

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

下面来看一个hibernate中典型的ThreadLocal的应用:

Java代码
1.private static final ThreadLocal threadSession = new ThreadLocal();
2.
3.public static Session getSession() throws InfrastructureException {
4. Session s = (Session) threadSession.get();
5. try {
6. if (s == null) {
7. s = getSessionFactory().openSession();
8. threadSession.set(s);
9. }
10. } catch (HibernateException ex) {
11. throw new InfrastructureException(ex);
12. }
13. return s;
14.}

可以看到在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源码)

Java代码
1.public class ThreadLocal<T> {
2. /**
3. * ThreadLocals rely on per-thread hash maps attached to each thread
4. * (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
5. * objects act as keys, searched via threadLocalHashCode. This is a
6. * custom hash code (useful only within ThreadLocalMaps) that eliminates
7. * collisions in the common case where consecutively constructed
8. * ThreadLocals are used by the same threads, while remaining well-behaved
9. * in less common cases.
10. */
11. private final int threadLocalHashCode = nextHashCode();
12.
13. /**
14. * The next hash code to be given out. Accessed only by like-named method.
15. */
16. private static int nextHashCode = 0;
17.
18. /**
19. * The difference between successively generated hash codes - turns
20. * implicit sequential thread-local IDs into near-optimally spread
21. * multiplicative hash values for power-of-two-sized tables.
22. */
23. private static final int HASH_INCREMENT = 0x61c88647;
24.
25. /**
26. * Compute the next hash code. The static synchronization used here
27. * should not be a performance bottleneck. When ThreadLocals are
28. * generated in different threads at a fast enough rate to regularly
29. * contend on this lock, memory contention is by far a more serious
30. * problem than lock contention.
31. */
32. private static synchronized int nextHashCode() {
33. int h = nextHashCode;
34. nextHashCode = h + HASH_INCREMENT;
35. return h;
36. }
37.
38. /**
39. * Creates a thread local variable.
40. */
41. public ThreadLocal() {
42. }
43.
44. /**
45. * Returns the value in the current thread's copy of this thread-local
46. * variable. Creates and initializes the copy if this is the first time
47. * the thread has called this method.
48. *
49. * @return the current thread's value of this thread-local
50. */
51. public T get() {
52. Thread t = Thread.currentThread();
53. ThreadLocalMap map = getMap(t);
54. if (map != null)
55. return (T)map.get(this);
56.
57. // Maps are constructed lazily. if the map for this thread
58. // doesn't exist, create it, with this ThreadLocal and its
59. // initial value as its only entry.
60. T value = initialValue();
61. createMap(t, value);
62. return value;
63. }
64.
65. /**
66. * Sets the current thread's copy of this thread-local variable
67. * to the specified value. Many applications will have no need for
68. * this functionality, relying solely on the {@link #initialValue}
69. * method to set the values of thread-locals.
70. *
71. * @param value the value to be stored in the current threads' copy of
72. * this thread-local.
73. */
74. public void set(T value) {
75. Thread t = Thread.currentThread();
76. ThreadLocalMap map = getMap(t);
77. if (map != null)
78. map.set(this, value);
79. else
80. createMap(t, value);
81. }
82.
83. /**
84. * Get the map associated with a ThreadLocal. Overridden in
85. * InheritableThreadLocal.
86. *
87. * @param t the current thread
88. * @return the map
89. */
90. ThreadLocalMap getMap(Thread t) {
91. return t.threadLocals;
92. }
93.
94. /**
95. * Create the map associated with a ThreadLocal. Overridden in
96. * InheritableThreadLocal.
97. *
98. * @param t the current thread
99. * @param firstValue value for the initial entry of the map
100. * @param map the map to store.
101. */
102. void createMap(Thread t, T firstValue) {
103. t.threadLocals = new ThreadLocalMap(this, firstValue);
104. }
105.
106. .......
107.
108. /**
109. * ThreadLocalMap is a customized hash map suitable only for
110. * maintaining thread local values. No operations are exported
111. * outside of the ThreadLocal class. The class is package private to
112. * allow declaration of fields in class Thread. To help deal with
113. * very large and long-lived usages, the hash table entries use
114. * WeakReferences for keys. However, since reference queues are not
115. * used, stale entries are guaranteed to be removed only when
116. * the table starts running out of space.
117. */
118. static class ThreadLocalMap {
119.
120. ........
121.
122. }
123.
124.}


可以看到ThreadLocal类中的变量只有这3个int型:

Java代码
1.private final int threadLocalHashCode = nextHashCode();
2.private static int nextHashCode = 0;
3.private static final int HASH_INCREMENT = 0x61c88647;

而作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。

可以来看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,从上面看到构造函数ThreadLocal()里什么操作都没有,唯一的操作是这句:


Java代码
1.private final int threadLocalHashCode = nextHashCode();

那么nextHashCode()做了什么呢:

Java代码
1.private static synchronized int nextHashCode() {
2. int h = nextHashCode;
3. nextHashCode = h + HASH_INCREMENT;
4. return h;
5.}
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。

因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?

看一下上面的set()方法,两句合并一下成为

Java代码
1.ThreadLocalMap map = Thread.currentThread().threadLocals;

这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:

Java代码
1.public class Thread implements Runnable {
2. ......
3.
4. /* ThreadLocal values pertaining to this thread. This map is maintained
5. * by the ThreadLocal class. */
6. ThreadLocal.ThreadLocalMap threadLocals = null;
7. ......
8.}


再看这句:

Java代码
1.if (map != null)
2. map.set(this, value);

也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样大家看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。


ThreadLocal示例

为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:



Java代码
1.import java.text.DateFormat;
2.import java.text.ParseException;
3.import java.text.SimpleDateFormat;
4.import java.util.Date;
5./**
6. * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。
7. * @author
8. *
9. */
10.public class DateUtil {
11.
12. private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
13.
14. @SuppressWarnings("rawtypes")
15. private static ThreadLocal threadLocal = new ThreadLocal() {
16. protected synchronized Object initialValue() {
17. return new SimpleDateFormat(DATE_FORMAT);
18. }
19. };
20.
21. public static DateFormat getDateFormat() {
22. return (DateFormat) threadLocal.get();
23. }
24.
25. public static Date parse(String textDate) throws ParseException {
26. return getDateFormat().parse(textDate);
27. }
28.}



创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建;



Java代码
1.//第一次调用get将返回null
2.private static ThreadLocal threadLocal = new ThreadLocal();
3.//获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中
4.public static DateFormat getDateFormat()
5.{
6. DateFormat df = (DateFormat) threadLocal.get();
7. if(df==null){
8. df = new SimpleDateFormat(DATE_FORMAT)
9. threadLocal.set(df);
10. }
11. return df;
12.}



我们看下我们覆盖的initialValue方法:




Java代码
1.protected T initialValue() {
2. return null;//直接返回null
3. }

实例2

ThreadLocal的作用和在每个Thread类声明一个字段相同,那么什么时候使用它呢?还是在编写一些框架时,因为这时你无法预先定义Thread类。其中一个典型的用法是调用一个静态方法,这个静态方法会操作一个ThreadLocal变量,而不同的线程去调用时,访问的就是不同的副本。
下面通过这样一个例子来说明:模拟一个游戏,预先随机设定一个[1, 10]的整数,然后每个玩家去猜这个数字,每个玩家不知道其他玩家的猜测结果,看谁用最少的次数猜中这个数字。
这个游戏确实比较无聊,不过这里恰好可以把每个玩家作为一个线程,然后用ThreadLocal来记录玩家猜测的历史记录,这样就很容易理解ThreadLocal的作用。
Judge:用来设定目标数字以及判断猜测的结果。Player:每个Player作为一个线程,多个Player并行地去尝试猜测,猜中时线程终止。Attempt:具有ThreadLocal字段和猜测动作静态方法的类,ThreadLocal用于保存猜过的数字。Record:保存历史记录的数据结构,有一个List<Integer>字段。
ThreadLocal为了可以兼容各种类型的数据,实际的内容是再通过set和get操作的对象,详见Attempt的getRecord()。运行的时候,每个Player Thread都是去调用Attemp.guess()方法,进而操作同一个ThreadLocal变量history,但却可以保存每个线程自己的数据,这就是ThreadLocal的作用。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ThreadLocalTest {
    
    public static void main(String[] args) {
        Judge.prepare();
        new Player(1).start();
        new Player(2).start();
        new Player(3).start();
    }
    
}

class Judge {
    
    public static int MAX_VALUE = 10;
    private static int targetValue;
    
    public static void prepare() {
        Random random = new Random();
        targetValue = random.nextInt(MAX_VALUE) + 1;
    }
    
    public static boolean judge(int value) {
        return value == targetValue;
    }
    
}

class Player extends Thread {
    
    private int playerId;
    
    public Player(int playerId) {
        this.playerId = playerId;
    }
    
    @Override
    public void run() {
        boolean success = false;
        while(!success) {
            int value = Attempt.guess(Judge.MAX_VALUE);
            success = Judge.judge(value);
            System.out.println(String.format("Plyaer %s Attempts %s and %s", playerId, value, success ? " Success" : "Failed"));
        }
        Attempt.review(String.format("[IFNO] Plyaer %s Completed by ", playerId));
    }
    
}

class Attempt {
    
    private static ThreadLocal<Record> history = new ThreadLocal<Record>();
    
    public static int guess(int maxValue) {
        Record record = getRecord();
        Random random = new Random();
        int value = 0;
        do {
            value = random.nextInt(maxValue) + 1;
        } while (record.contains(value));
        record.save(value);
        return value;
    }
    
    public static void review(String info) {
        System.out.println(info + getRecord());
    }
    
    private static Record getRecord() {
        Record record = history.get();
        if(record == null) {
            record = new Record();
            history.set(record);
        }
        return record;
    }
    
}

class Record {
    
    private List<Integer> attemptList = new ArrayList<Integer>();;
    
    public void save(int value) {
        attemptList.add(value);
    }
    
    public boolean contains(int value) {
        return attemptList.contains(value);
    }
    
    @Override
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append(attemptList.size() + " Times: ");
        int count = 1;
        for(Integer attempt : attemptList) {
            buffer.append(attempt);
            if(count < attemptList.size()) {
                buffer.append(", ");
                count++;
            }
        }
        return buffer.toString();
    }
    
}

运行结果
Plyaer 2 Attempts 8 and Failed
Plyaer 3 Attempts 6 and Failed
Plyaer 1 Attempts 5 and Failed
Plyaer 2 Attempts 7 and  Success
Plyaer 3 Attempts 9 and Failed
Plyaer 1 Attempts 9 and Failed
Plyaer 3 Attempts 2 and Failed
Plyaer 1 Attempts 2 and Failed
[IFNO] Plyaer 2 Completed by 2 Times: 8, 7
Plyaer 3 Attempts 4 and Failed
Plyaer 1 Attempts 1 and Failed
Plyaer 3 Attempts 5 and Failed
Plyaer 1 Attempts 3 and Failed
Plyaer 3 Attempts 1 and Failed
Plyaer 1 Attempts 10 and Failed
Plyaer 3 Attempts 8 and Failed
Plyaer 1 Attempts 6 and Failed
Plyaer 3 Attempts 7 and  Success
Plyaer 1 Attempts 4 and Failed
[IFNO] Plyaer 3 Completed by 8 Times: 6, 9, 2, 4, 5, 1, 8, 7
Plyaer 1 Attempts 7 and  Success
[IFNO] Plyaer 1 Completed by 9 Times: 5, 9, 2, 1, 3, 10, 6, 4, 7

关于ThreadLocal的原理,可以从其get()方法的实现来看
public class ThreadLocal<T> {
    ...
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    ...
}

执行get()时首先获取当前的Thread,再获取Thread中的ThreadLocalMap - t.threadLocals,并以自身为key取出实际的value。于是可以看出,ThreadLocal的变量实际还是保存在Thread中的,容器是一个Map,Thread用到多少ThreadLocal变量,就会有多少以其为key的Entry。

------------------------

ThreadLocal的几种误区

最近由于需要用到ThreadLocal,在网上搜索了一些相关资料,发现对ThreadLocal经常会有下面几种误解

一、ThreadLocal是java线程的一个实现
ThreadLocal的确是和java线程有关,不过它并不是java线程的一个实现,它只是用来维护本地变量。针对每个线程,提供自己的变量版本,主要是为了避免线程冲突,每个线程维护自己的版本。彼此独立,修改不会影响到对方。

二、ThreadLocal是相对于每个session的

ThreadLocal顾名思义,是针对线程。在java web编程上,每个用户从开始到会话结束,都有自己的一个session标识。但是ThreadLocal并不是在会话层上。其实,Threadlocal是独立于用户session的。它是一种服务器端行为,当服务器每生成一个新的线程时,就会维护自己的ThreadLocal。对于这个误解,个人认为应该是开发人员在本地基于一些应用服务器测试的结果。众所周知,一般的应用服务器都会维护一套线程池,也就是说,对于每次访问,并不一定就新生成一个线程。而是自己有一个线程缓存池。对于访问,先从缓存池里面找到已有的线程,如果已经用光,才去新生成新的线程。所以,由于开发人员自己在测试时,一般只有他自己在测,这样服务器的负担很小,这样导致每次访问可能是共用同样一个线程,导致会有这样的误解:每个session有一个ThreadLocal

三、ThreadLocal是相对于每个线程的,用户每次访问会有新的ThreadLocal

理论上来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量

四、对每个用户访问,ThreadLocal可以多用
可以说,ThreadLocal是一把双刃剑,用得来的话可以起到非常好的效果。但是,ThreadLocal如果用得不好,就会跟全局变量一样。代码不能重用,不能独立测试。因为,一些本来可以重用的类,现在依赖于ThreadLocal变量。如果在其他没有ThreadLocal场合,这些类就变得不可用了。个人觉得ThreadLocal用得很好的几个应用场合,值得参考

1、存放当前session用户:quake want的jert

2、存放一些context变量,比如webwork的ActionContext

3、存放session,比如Spring hibernate orm的session
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: