1 线程封闭
多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了。
数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭。
避免并发异常最简单的方法就是线程封闭。即把对象封装到一个线程里,只有该线程能看到此对象。
1.1 栈封闭
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
1.2 ThreadLocal实现线程封闭
ThreadLocal是Java里一种特殊的变量。 它是一个线程级变量,每个线程都有一个ThreadLocal,就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
- 用法
ThreadLocal<T> var = new ThreadLocal<T>();
会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。
可以用 ThreadLocal 存储一些参数, 以便在线程中的多个方法中使用,用来代替方法传参。
实例
hreadLocal内部维护了一个Map,Map的key是每个线程的名称,Map的value就是我们要封闭的对象。
每个线程中的对象都对应着Map中一个值,即ThreadLocal利用Map实现了对象的线程封闭。
对于CS游戏,开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为1500、0、10。
设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
如果共享,那么线程之间的并发修改会导致数据不准确。能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的,这个对象就是ThreadLocal。
注意不能将其翻译为线程本地化或本地线程,英语恰当的名称应该叫作:CopyValueIntoEveryThread。
示例代码
JVM维护了一个Map<Thread, T>,每个线程要用这个T时,用当前的线程去Map里面取。
该示例中,无 set 操作,那么初始值又是如何进入每个线程成为独立拷贝的呢?
虽然ThreadLocal在定义时重写了initialValue(),但并非是在BULLET_ NUMBER_ THREADLOCAL对象加载静态变量的时候执行。而是每个线程在ThreadLocal.get()时都会执行到。
其源码如下
每个线程都有自己的ThreadLocalMap;
如果map ==null,则直接执行setInitialValue();
如果 map 已创建,就表示 Thread 类的threadLocals 属性已初始化完毕;
如果 e==null,依然会执行到setinitialValue()
setinitialValue()的源码如下:
这是一个保护方法,CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue()
getMap的源码就是提取线程对象t的ThreadLocalMap属性: t. threadLocals.
在CsGameByThreadLocal第1处,使用了ThreadLocalRandom 生成单独的Random实例;
该类在JDK7中引入,它使得每个线程都可以有自己的随机数生成器。我们要避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed而导致性能下降。
ThreadLocal是每一个线程单独持有的,因为每一个线程都有独立的变量副本,其他线程不能访问,所以不存在线程安全问题,也不会影响程序的执行性能。
ThreadLocal对象通常是由private static修饰的,因为都需要复制到本地线程,所以非static作用不大。
ThreadLocal无法解决共享对象的更新问题,下面的实例将证明这点:
因为CsGameByThreadLocal中使用的是Integer不可变对象,所以可使用相同的编码方式来操作一下可变对象看看
输出的结果是乱序不可控的,所以使用某个引用来操作共享对象时,依然需要进行线程同步
ThreadLocal 有个静态内部类ThreadLocalMap,它还有一个静态内部类Entry;
在Thread中的ThreadLocalMap属性的赋值是在ThreadLocal类中的createMap.
ThreadLocal与ThreadLocalMap有三组对应的方法: get()、set()和remove();
在ThreadLocal中对它们只做校验和判断,最终的实现会落在ThreadLocalMap..
Entry继承自WeakReference,只有一个value成员变量,它的key是ThreadLocal对象
再从栈与堆的内存角度看看两者的关系
一个Thread有且仅有一个ThreadLocalMap对象
一个Entry对象的 key 弱引用指向一个ThreadLocal对象
一个ThreadLocalMap对象存储多个Entry 对象
一个ThreadLocal 对象可被多个线程共享
ThreadLocal对象不持有Value,Value 由线程的Entry 对象持有.
Entry 对象源码如下
所有的Entry对象都被ThreadLocalMap类实例化对象threadLocals持有;
当线程执行完毕时,线程内的实例属性均会被垃圾回收,弱引用的ThreadLocal,即使线程正在执行,只要ThreadLocal对象引用被置成null,Entry的Key就会自动在下一次Y - GC时被垃圾回收;
而在ThreadLocal使用set()/get()时,又会自动将那些key=null的value 置为null,使value能够被GC,避免内存泄漏,现实很骨感, ThreadLocal如源码注释所述:
ThreadLocal
对象通常作为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束.