三、ThreadLocal 线程本地变量
ThreadLocal 与Synchonized 都是用于解决多线程并发安全问题的,只是synchronized利用的锁的机制,使变量或代码块在某一时刻只能由一个线程访问。
ThreadLocal 为每个线程提供了变量的副本,副本只有所属线程才可以操作。这样就隔离了多个线程对数据的数据共享。
spring 的事务就是借助ThreadLocal类,后续spring 源码篇会详细解释。
ThreadLocal 使用起来很简单,常用只有4个方法:
public T get():获取当前线程所对应的线程局部变量。
public void set(T value):设置当前线程的线程局部变量的。
public void remove():删除当前线程局部变量,@since 1.5 新加方法。我们知道ThreadLocal 是线程局部变量,所以说线程销毁后GC 会回收ThreadLocal,所以这个方法非必须调用,使用它只是为了加快内存回收的速度。
protected T initialValue():返回当前线程的“初始值”,该方法是一个protected,显然是为了让子类重新的而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null
ThreadLocal 解析
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取线程中的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
上面线程先是获取当前线程,然后调用了getMap(t) 获取了线程中的ThreadLocalMap(面试重灾区)。
ThreadLocalMap 是ThreadLocal 的静态内部类,然而ThreadLocalMap 并不属于ThreadLocal 而是需要传入Thread
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们打开Thread 类
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 是Thread 的成员变量。
那么ThreadLocalMap 的内部实现又是什么呢?
static class ThreadLocalMap {
//WeakReference 弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//类似map 的key,value 结构,其中的key 是ThreadLocal
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
内部维护了一个Entry的内部类。Entry 类似map 的K,V 结构,其中的key 是ThreadLocal,value就是需要保存的变量。
/**
* 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;
使用数组保存了Entry,初始的容量为16,由于可能保存的变量为多个。
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);
}
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();
}
getEntry 方法 则是获取某个 ThreadLocal 对应的值,set 方法就是更新或赋值相应的 ThreadLocal 对应的值。
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取线程中的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //如果没有拿到map 那么进行初始化
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
回到我们get 方法,其实就是拿到每个线程的ThreadLocalMap,然后通过ThreadLocal为key获取到对应的value。如果没有获取到map 的话那么进行初始化等一些工作。
由于ThreadLocal 使用比较简单就不做演示了,基本就是定义全局变量就可以了
private static ThreadLocal<T> value = new ThreadLocal<T>() {
};
内存泄漏分析
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们可以看到ThreadLocalMap 的Entry 继承了一个WeakReference 弱引用,所以就为了ThreadLocal 使用不当引起的内存泄漏埋下伏笔了。java中的引用类型分为
强引用:就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。
软引用:是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。
虚引用:也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。
场景一
使用线程池投入无业务线程 设置最大堆大小为 -Xmx256m
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
private static ThreadLocal<LocalVariable> local;
private static ExecutorService pool = Executors.newFixedThreadPool(5);
//本地变量填充
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/* 5M大小的数组 */
}
static class MyThread implements Runnable {
public void run() {
// TODO Auto-generated method stub
// local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
// local.set(new LocalVariable());
// new LocalVariable();
// local.remove();
System.out.println("MyThread start");
}
}
public static void main(String[] args) throws Exception {
Thread.sleep(2000);
for(int i=0;i<100;i++) {
Thread.sleep(200);
pool.execute(new MyThread());
}
Thread.sleep(2000);
System.out.println("执行结束了");
}
}
堆使用一直在25m 左右。
场景二
在线程中投入填充5m 大小byte
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
private static ThreadLocal<LocalVariable> local;
private static ExecutorService pool = Executors.newFixedThreadPool(5);
//本地变量填充
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/* 5M大小的数组 */
}
static class MyThread implements Runnable {
public void run() {
// TODO Auto-generated method stub
// local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
// local.set(new LocalVariable());
new LocalVariable();
// local.remove();
System.out.println("MyThread start");
}
}
public static void main(String[] args) throws Exception {
Thread.sleep(2000);
for(int i=0;i<100;i++) {
Thread.sleep(200);
pool.execute(new MyThread());
}
Thread.sleep(2000);
System.out.println("执行结束了");
}
}
内存使用跟一类似。
场景三
使用ThreadLocal 保存数据
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
private static ThreadLocal<LocalVariable> local;
private static ExecutorService pool = Executors.newFixedThreadPool(5);
//本地变量填充
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/* 5M大小的数组 */
}
static class MyThread implements Runnable {
public void run() {
// TODO Auto-generated method stub
local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
local.set(new LocalVariable());
// new LocalVariable();
// local.remove();
System.out.println("MyThread start");
}
}
public static void main(String[] args) throws Exception {
Thread.sleep(2000);
for(int i=0;i<100;i++) {
Thread.sleep(200);
pool.execute(new MyThread());
}
Thread.sleep(2000);
System.out.println("执行结束了");
}
}
内存变为了100M 左右
场景四
使用ThreadLocal 保存数据,调用remove 方法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
private static ThreadLocal<LocalVariable> local;
private static ExecutorService pool = Executors.newFixedThreadPool(5);
//本地变量填充
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/* 5M大小的数组 */
}
static class MyThread implements Runnable {
public void run() {
// TODO Auto-generated method stub
local = new ThreadLocal<ThreadLocalTest.LocalVariable>();
local.set(new LocalVariable());
// new LocalVariable();
local.remove();
System.out.println("MyThread start");
}
}
public static void main(String[] args) throws Exception {
Thread.sleep(2000);
for(int i=0;i<100;i++) {
Thread.sleep(200);
pool.execute(new MyThread());
}
Thread.sleep(2000);
System.out.println("执行结束了");
}
}
内存基本跟场景一二保持一致
由此可以看到场景三使用ThreadLocal 发生的内存泄漏。由于ThreadLocal 内存泄漏原因分析网上一堆,这边就不分析了可以参考 ThreadLocal为什么会导致内存泄漏? - Chen洋 - 博客园