ThreadLocal
除了将变量定义为immutable以外,ThreadLocal提供了新的线程安全思路。
ThreadLocal实现线程安全是将变量独立的保存到每个线程中,这样就不存在竞争问题,也就用不到互斥锁了;同时每个线程中都有一个变量的独立副本(该副本对其他线程不可见),也不用考虑线程安全问题。
实现上,ThreadLocal类中没有存储结构,ThreadLocal依赖于当前线程中的threadLocalMap存储
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//ThreadLocalMap已经被初始化,已当前threadLocal对象为key,添加value
map.set(this, value);
} else {
//如果未初始化,则新建表
createMap(t, value);
}
}
通俗一点总结,Thread中存在一张ThreadLocal变量表,每新建一个ThreadLocal对象,就会向变量表中添加一个变量,每个线程的变量表独立不共享,从而实现线程安全。
内存泄漏
首先我们看一下ThreadLocalMap的内部类Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
也就是说,Map中作为key的ThreadLocal是个弱引用,下次GC就没了,就变成了(null,value)
也就是图中的虚线部分,这时候对于ThreadLocal来说,这个value就是不可见的,应当回收
但在线程池中,线程可以长时间存在,每个线程持有threadLocalMap的强引用,进而导致无效键值对(null,value)被判断为可达,也就不会被释放,从而产生内存泄漏。
因此在时候ThreadLocal时,要及时remove已经set的值
如果key设计为强引用是否可以避免内存泄漏的发生呢?
事实上设计成弱引用是有道理的。如果key和ThreadLocal之间是强引用,根据可达性分析,除非当前线程结束,否则TheadLocal是永远不会被收回的,这又造成了内存泄漏
原子类
原子类的实现依赖于乐观锁CAS
CAS
所谓的乐观锁,实际就是不上锁,不断循环重试条件,直到达到条件。
CAS全程compare and swap,可以用三元组(V,O,N)描述。其中V表示操作变量的地址,O是预期的旧址(将要被替换的值),N是预期的新值(将要替换的值)。此外CAS还是一个原子操作,其由一条底层指令CMPXCHG 实现。
但是CAS仍存在一定的问题:ABA
CAS比较旧值是否发生变化 作为 是否被修改过的依据。可如果修改过程为A->B->A,旧值没有变化但是的确被修改过,这样的修改是CAS不能分辨的,也就是ABA问题。
对于这个问题的解决 可以添加版本号/时间戳来避免
以下以AtomicInteger
为例进行介绍
常用方法
// 替换新值,返回旧值
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
//自增,返回旧值
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
//自增,返回新值
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// 相加,返回旧值
public final int addAndGet(int delta) {
return U.getAndAddInt(this, VALUE, delta) + delta;
}
参考链接: