多线程访问共享可变数据时,涉及到线程间数据同步的问题。并非全部时候,都要用到共享数据,因此线程封闭概念就提出来了。apache
数据都被封闭在各自的线程之中,就不须要同步,这种经过将数据封闭在线程中而避免使用同步的技术称为线程封闭。编程
避免并发异常最简单的方法就是线程封闭即 把对象封装到一个线程里,只有该线程能看到此对象;那么该对象就算非线程安全,也不会出现任何并发安全问题.数组
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其余线程没法访问这个栈缓存
ThreadLocal
是实现线程封闭的最佳实践.ThreadLocal是Java里一种特殊的变量。它是一个线程级变量,每一个线程都有一个ThreadLocal, 就是每一个线程都拥有了本身独立的一个变量,竞争条件被完全消除了,在并发模式下是绝对安全的变量。安全
ThreadLocal<T> var = new ThreadLocal<T>();复制代码
会自动在每个线程上建立一个T的副本,副本之间彼此独立,互不影响。能够用 ThreadLocal 存储一些参数, 以便在线程中多个方法中使用,用来代替方法传参的作法。session
实例ThreadLocal
内部维护了一个Map,Map的key是每一个线程的名称,Map的值就是咱们要封闭的对象.每一个线程中的对象都对应着Map中一个值,也就是ThreadLocal
利用Map实现了对象的线程封闭.多线程
对于CS游戏,开始时,每一个人可以领到一把枪,枪把上有三个数字:子弹数、杀敌数、本身的命数,为其设置的初始值分别为1500、0、10.并发
设战场上的每一个人都是一个线程,那么这三个初始值写在哪里呢?若是每一个线程都写死这三个值,万一将初始子弹数统一改为 1000发呢?若是共享,那么线程之间的并发修改会致使数据不许确.能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,可是每一个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal框架
注意不能将其翻译为线程本地化或本地线程
英语恰当的名称应该叫做:CopyValueIntoEveryThreaddom
示例代码
实在难以理解的,能够理解为,JVM维护了一个Map
,每一个线程要用这个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
对象一般做为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束.
ThreadLocal
, 很容易引发脏数据问题 ThreadLocal
对象是没有意义的 若是ThreadLocal
是非静态的,属于某个线程实例,那就失去了线程间共享的本质属性;那么ThreadLocal
到底有什么做用呢?咱们知道,局部变量在方法内各个代码块间进行传递,而类变量在类内方法间进行传递;复杂的线程方法可能须要调用不少方法来实现某个功能,这时候用什么来传递线程内变量呢?即ThreadLocal
,它一般用于同一个线程内,跨类、跨方法传递数据;若是没有ThreadLocal,那么相互之间的信息传递,势必要靠返回值和参数,这样无形之中,有些类甚至有些框架会互相耦合;经过将Thread构造方法的最后一个参数设置为true,能够把当前线程的变量继续往下传递给它建立的子线程
public Thread (ThreadGroup group, Runnable target, String name,long stackSize, boolean inheritThreadLocals) [
this (group, target, name, stackSize, null, inheritThreadLocals) ;
}复制代码
parent为其父线程
if (inheritThreadLocals && parent. inheritableThreadLocals != null)
this. inheritableThreadLocals = ThreadLocal. createInheritedMap (parent. inheritableThreadLocals) ;复制代码
createlnheritedMap()
其实就是调用ThreadLocalMap
的私有构造方法来产生一个实例对象,把父线程中不为null
的线程变量都拷贝过来
private ThreadLocalMap (ThreadLocalMap parentMap) {
// table就是存储
Entry[] parentTable = parentMap. table;
int len = parentTable. length;
setThreshold(len) ;
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
ThreadLocal<object> key = (ThreadLocal<object>) e.get() ;
if (key != null) {
object value = key. childValue(e.value) ;
Entry c = new Entry(key, value) ;
int h = key. threadLocalHashCode & (len - 1) ;
while (table[h] != null)
h = nextIndex(h, len) ;
table[h] = C;
size++;
}
}
}复制代码
不少场景下可经过ThreadLocal
来透传全局上下文的;好比用ThreadLocal
来存储监控系统的某个标记位,暂且命名为traceld.某次请求下全部的traceld都是一致的,以得到能够统一解析的日志文件;但在实际开发过程当中,发现子线程里的traceld为null,跟主线程的traceld并不一致,因此这就须要刚才说到的InheritableThreadLocal
来解决父子线程之间共享线程变量的问题,使整个链接过程当中的traceld一致.示例代码以下
import org.apache.commons.lang3.StringUtils;
/**
* @author sss
* @date 2019/1/17
*/
public class RequestProcessTrace {
private static final InheritableThreadLocal<FullLinkContext> FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL
= new InheritableThreadLocal<FullLinkContext>();
public static FullLinkContext getContext() {
FullLinkContext fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get();
if (fullLinkContext == null) {
FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.set(new FullLinkContext());
fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get();
}
return fullLinkContext;
}
private static class FullLinkContext {
private String traceId;
public String getTraceId() {
if (StringUtils.isEmpty(traceId)) {
FrameWork.startTrace(null, "JavaEdge");
traceId = FrameWork.getTraceId();
}
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
}
}复制代码
使用ThreadLocal
和InheritableThreadLocal
透传上下文时,须要注意线程间切换、异常传输时的处理,避免在传输过程当中因处理不当而致使的上下文丢失.
最后,SimpleDateFormat
是非线程安全的类,定义为static,会有数据同步风险.经过源码能够看出,SimpleDateFormat
内部有一个Calendar
对象;在日期转字符串或字符串转日期的过程当中,多线程共享时极可能产生错误;推荐使用 ThreadLocal
,让每一个线程单独拥有这个对象.
为了使线程安全地共享某个变量,JDK给出了ThreadLocal
.但ThreadLocal
的主要问题是会产生脏数据和内存泄漏;这两个问题一般是在线程池的线程中使用ThreadLocal引起的,由于线程池有线程复用和内存常驻两是在线程池的线程中使用ThreadLocal 引起的,由于线程池有线程复用和内存常驻两个特色
线程复用会产生脏数据;因为线程池会重用 Thread
对象,与 Thread
绑定的静态属性 ThreadLoca
l变量也会被重用.若是在实现的线程run()方法中不显式调用remove()
清理与线程相关的ThreadLocal
信息,那么若下一个线程不调用set()
,就可能get()
到重用的线程信息;包括ThreadLocal
所关联的线程对象的value值.
脏读问题其实十分常见.好比,用户A下单后没有看到订单记录,而用户B却看到了用户A的订单记录.经过排查发现是因为 session 优化引起.在原来的请求过程当中,用户每次请求Server,都须要经过 sessionId 去缓存里查询用户的session信息,这样无疑增长了一次调用.所以,工程师决定采用某框架来缓存每一个用户对应的SecurityContext
, 它封装了session 相关信息.优化后虽然会为每一个用户新建一个 session 相关的上下文,但因为Threadlocal
没有在线程处理结束时及时remove()
;在高并发场景下,线程池中的线程可能会读取到上一个线程缓存的用户信息.
在源码注释中提示使用static关键字来修饰ThreadLocal
.在此场景下,寄但愿于ThreadLocal
对象失去引用后,触发弱引用机制来回收Entry
的Value
就不现实了.在上例中,若是不进行remove()
,那么当该线程执行完成后,经过ThreadLocal
对象持有的String对象是不会被释放的.
remove()
清理** 该类提供了线程局部 (thread-local) 变量;这些变量不一样于它们的普通对应物,由于访问某变量(经过其 get /set 方法)的每一个线程都有本身的局部变量,它独立于变量的初始化副本.
ThreadLocal
实例一般是类中的 private static
字段,但愿将状态与某一个线程(e.g. 用户 ID 或事务 ID)相关联.
一个以ThreadLocal
对象为键、任意对象为值的存储结构;有点像HashMap
,能够保存"key : value"键值对,但一个ThreadLocal
只能保存一个键值对,各个线程的数据互不干扰.该结构被附带在线程上,也就是说一个线程能够根据一个ThreadLocal
对象查询到绑定在这个线程上的一个值.
ThreadLocal<String> localName = new ThreadLocal();
localName.set("JavaEdge");
String name = localName.get();复制代码
在线程A中初始化了一个ThreadLocal对象localName,并set了一个值JavaEdge;同时在线程A中经过get可拿到以前设置的值;可是若是在线程B中,拿到的将是一个null.
由于ThreadLocal
保证了各个线程的数据互不干扰看看set(T value)和get()方法的源码
可见,每一个线程中都有一个ThreadLocalMap
threadLocals
变量 threadLocals
变量获取 因此在线程A中set的值,是线程B永远得不到的即便在线程B中从新set,也不会影响A中的值;保证了线程之间不会相互干扰.
从名字上看猜它相似HashMap,但在ThreadLocal
中,并没有实现Map接口
ThreadLoalMap
中,也是初始化一个大小为16的Entry数组ThreadLocal
的set()
,把ThreadLocal
对象自身当作key,放进ThreadLoalMap
ThreadLoalMap
的Entry
继承WeakReference
Entry
中没有next
字段,因此不存在链表情形. 无链表,那发生hash冲突时何解?
先看看ThreadLoalMap
插入一个 key/value 的实现
ThreadLocal
对象都有一个hash值 - threadLocalHashCode
ThreadLocal
对象,hash值就增长一个固定大小在插入过程当中,根据ThreadLocal
对象的hash值,定位至table中的位置i.过程以下
Entry
对象的key正是将设置的key,覆盖其value(和HashMap 处理相同); 如此,在get
时,也会根据ThreadLocal
对象的hash值,定位到table中的位置.而后判断该位置Entry对象中的key是否和get的key一致,若是不一致,就判断下一个位置.
可见,set和get若是冲突严重的话,效率很低,由于ThreadLoalMap
是Thread的一个属性,因此即便在本身的代码中控制了设置的元素个数,但仍是不能控制其它代码的行为
ThreadLocal可能致使内存泄漏,为何?先看看Entry的实现:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}复制代码
经过以前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中
这就致使了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,若是建立ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
既然发现有内存泄露的隐患,天然有应对策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就能够被回收,固然若是调用remove方法,确定会删除对应的Entry对象。
若是使用ThreadLocal的set方法以后,没有显示的调用remove方法,就有可能发生内存泄露,因此养成良好的编程习惯十分重要,使用完ThreadLocal以后,记得调用remove方法。
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("JavaEdge");
// 其它业务逻辑
} finally {
localName.remove();
}复制代码
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的.通常状况下,经过set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的;各个线程中访问的是不一样的对象.
**另外,说ThreadLocal使得各线程可以保持各自独立的一个对象;并非经过set()实现的,而是经过每一个线程中的new 对象的操做来建立的对象,每一个线程建立一个,不是什么对象的拷贝或副本。**经过set()将这个新建立的对象的引用保存到各线程的本身的一个map中,每一个线程都有这样一个map;执行get()时,各线程从本身的map中取出放进去的对象,所以取出来的是各自线程中的对象.ThreadLocal实例是做为map的key来使用的.
若是set()进去的东西原本就是多个线程共享的同一个对象;那么多个线程的get()取得的仍是这个共享对象自己,仍是有并发访问问题。
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;
} 复制代码
首先判断当前线程中有没有放入 session,若是尚未,那么经过sessionFactory().openSession()
来建立一个session;再将session set()
到线程中,实际是放到当前线程的ThreadLocalMap
;这时,对于该 session 的惟一引用就是当前线程中的那个ThreadLocalMap;threadSession 做为这个值的key,要取得这个 session 能够经过threadSession.get();里面执行的操做实际是先取得当前线程中的ThreadLocalMap;而后将threadSession做为key将对应的值取出.这个 session 至关于线程的私有变量,而不是public的.
显然,其余线程中是取不到这个session的,他们也只能取到本身的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了.
可能就要在action中建立session,而后把session一个个传到service和dao中,这可够麻烦的;或者能够本身定义一个静态的map,将当前thread做为key,建立的session做为值,put到map中,应该也行,这也是通常人的想法.但事实上,ThreadLocal的实现恰好相反,它是在每一个线程中有一个map,而将ThreadLocal实例做为key,这样每一个map中的项数不多,并且当线程销毁时相应的东西也一块儿销毁了
总之,ThreadLocal
不是用来解决对象共享访问问题的;而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式
ThreadLocalMap
类对象;ThreadLocal
静态实例做为key,将不一样对象的引用保存到不一样线程的ThreadLocalMap中,而后在线程执行的各处经过这个静态ThreadLocal实例的get()方法取得本身线程保存的那个对象,避免了将这个对象做为参数传递的麻烦. 固然若是要把原本线程共享的对象经过set()放到线程中也能够,能够实现避免参数传递的访问方式;可是要注意get()到的是那同一个共享对象,并发访问问题要靠其余手段来解决;但通常来讲线程共享的对象经过设置为某类的静态变量就能够实现方便的访问了,彷佛不必放到线程中
我以为最适合的是按线程多实例(每一个线程对应一个实例)的对象的访问,而且这个对象不少地方都要用到。
能够看到ThreadLocal类中的变量只有这3个int型:
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647; 复制代码
而做为ThreadLocal实例的变量只有 threadLocalHashCodenextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量实际上
看一下建立一个ThreadLocal实例即new ThreadLocal()时作了哪些操做,构造方法ThreadLocal()
里什么操做都没有,惟一的操做是这句
private final int threadLocalHashCode = nextHashCode(); 复制代码
那么nextHashCode()作了什么呢
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}复制代码
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,而后nextHashCode的值增长HASH_INCREMENT这个值。.
所以ThreadLocal实例的变量只有这个threadLocalHashCode,并且是final的,用来区分不一样的ThreadLocal实例;ThreadLocal类主要是做为工具类来使用,那么set()进去的对象是放在哪儿的呢?
看一下上面的set()方法,两句合并一下成为
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;
......
} 复制代码
再看这句:
if (map != null)
map.set(this, value); 复制代码
也就是将该ThreadLocal实例做为key,要保持的对象做为值,设置到当前线程的ThreadLocalMap 中,get()方法一样看了代码也就明白了.
《码出高效:Java开发手册》