ThreadLocal线程安全可分为两个部分:一是,各线程的数据安全,二是,ThreadLocal线程中数据操作的线程安全。
关于什么是ThreadLocal,可网上搜索资料,原子操作类网上也有很多资料,以下为整理及总结。
1、ThreadLocal是什么
ThreadLocal可以保存”key : value”键值对,类似HashMap,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。
ThreadLocal<String> localName = new ThreadLocal();
localName.set("name");
String name = localName.get();
在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值,同时在线程1中通过 localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。这就是各线程数据的互不干扰。具体如何实现?
2、各线程数据的互不干扰
先看下, set(T value)和 get()方法的源码
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's 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);
}
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} 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) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
从set和get方法中可见两个方法都是先获取当前线程Thread.currentThread(),然后通过getMap获取ThreadLocalMap 。先看下getMap方法
/**
* 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;
}
其中,threadLocals为Thread类中的变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
可以发现,在getMap()中都有自己的ThreadLocalMap类型的变量threadLocals,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行get方法中,是从当前线程的threadLocals变量中获取值。然后在具体看下ThreadLocalMap,是如何保证了线程之间不会相互干扰的!
再来看下1.7版本中ThreadLocalMap 源码(部分),瞅一眼即可!
可以从源码中发现,在ThreadLoalMap中,也是初始化一个INITIAL_CAPACITY=16的变量Entry数组,初始的扩容阈值threshold=0,当数组不为空时,threshold = len * 2 / 3;当数组的size>0.75*threshold时进行扩容,扩容的方式和数组一致,对数组扩充一倍。ThreadLoalMap中的Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
/**
* 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 {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal key = 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++;
}
}
}
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
}
ThreadLoalMap的Entry是继承WeakReference,因此Entry中没有next字段,所以就不存在链表了,没有链表结构,那如何解决hash冲突,每个ThreadLocal对象都有一个hash值 threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小 0x61c88647。从下面的源码可以看到nextHashCode是通过原子操作类AtomicInteger增加值的,因此也是线程安全的。
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* 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;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
看下ThreadLoalMap中插入一个key-value的实现。
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下: 1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上; 2、不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value; 3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。
3、原子操作类保证数据操作的线程安全
上文中的hashCode是通过AtomicInteger 来增加固定值得到的。java提供的原子操作可以原子更新的基本类型有以下三个:AtomicBoolean,AtomicInteger,AtomicLong
看下getAndAdd()方法是如何利用原子操作实现线程安全的,
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
其中, get()是取得AtomicInteger里存储的数值。上面的过程一个无限循环,循环体当中做了三件事:
1.获取当前值。
2.当前值+delta,计算出目标值。
3.进行CAS操作,如果成功则跳出循环,如果失败则重复上述步骤。
compareAndSet是怎么保证线程安全的呢?compareAndSet方法的实现很简单,涉及到两个对象,一个是unsafe,一个是valueOffset。unsafe是JVM为我们提供了硬件级别的原子操作,valueOffset可理解为value变量的内存地址。正是通过这两个重要的对象来保证线程安全。
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
这里需要注意的重点是 get 方法,这个方法的作用是获取变量的当前值。
如何保证获得的当前值是内存中的最新值呢?用volatile关键字来保证。
private volatile int value;
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
有关volatile关键字的知识,可自行搜索,这里就不详细阐述了。volatile主要有两个方面功能,一是,防止指令重排,另一个是,获得的当前值是内存中的最新值。
ThreadLocal中的线程安全,有些方法是用synchronized来保证的,关于数据的操作是用原子类操作来完成的。原子类操作是CAS机制的一种,synchronized和原子类操作的区别可理解为同步锁synchronized和CAS的区别。
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
前文所说的compareAndSwapIntf方法参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。
当然,CAS也存在一些缺点,比如ABA问题,旧值A,新值B,两个线程一和二同时需要修改A为B,当线程一执行完成,值已经修改为B,此时第三个线程修改值B为 A,那么线程二又可以把值A修改为B。不过此类问题已经通过添加版本号来解决了。其次,CAS存在CPU开销较大,不能保证代码块的原子性等一些问题。