1.简单介绍
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
下面是示例:
public class ThreadLocalTest {
/*1通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 */
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
/*2获取下一个序列值 */
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String args[]){
System.out.println("ThreadLocalTest start");
ThreadLocalTest sn = new ThreadLocalTest();
/*3个线程共享sn,各自产生序列号 */
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private ThreadLocalTest sn;
public TestClient(ThreadLocalTest sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {
/*4每个线程打出3个序列值 */
System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["
+ sn.getNextNum() + "]");
}
}
}
}
使用例子:
/**sdf有全局变量线程不安全,用ThreadLocal提供线程安全的sdf*/
public static final ThreadLocal<DateFormat> SDF = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
}
};
2.比较优秀的使用例子2(重要):
public class Port implements IThreadCase{
//当前线程的Port实例
private final static ThreadLocal<Port> portCurrent = new ThreadLocal<>();
//线程管理类
private final ThreadHandler thread;
public Port(){
this.thread = new ThreadHandler(this);
}
/** 开始 */
public Port startup() {
// 启动独立线程
this.thread.setName(toString());
this.thread.startup();
return this;
}
/** 结束 */
public void stop() {
if (thread == null)
return;
// 停止独立线程
this.thread.cleanup();
}
/** 获取当前线程的Port实例 */
@SuppressWarnings("unchecked")
public static <T extends Port> T getCurrent() {
return (T) portCurrent.get();
}
@Override
public void caseStart() {
portCurrent.set(this);
}
@Override
public void caseStop() {
portCurrent.set(null);
}
}
3. ThreadLocal会造成内存泄漏吗?
不同线程反复对ThreadLocal设置自己的值,线程会自己结束,但是没有对ThreadLocal存储本线程的内容做移除,会造成内存泄漏吗。
答案是不会的。
示例代码:
public class ThreadLocalTest {
public static final int _10MB = 1024 * 1024 * 10;
public static ThreadLocal<Object> threadLocal = new ThreadLocal(){
@Override
public Object initialValue(){
return null;
}
};
public static void main(String args[]){System.out.println();
for(;;){
new Thread(){
@Override
public void run() {
byte[] data = new byte[_10MB];
threadLocal.set(data);
}
}.start();
ThreadTool.sleep(1000);
System.out.println(Sys.getJVMStatus());
}
}
}
运行结果:
maxM=3641 MB, totalM=245.5 MB, freeM=231.66 MB, usedM=13.84 MB
maxM=3641 MB, totalM=245.5 MB, freeM=220.38 MB, usedM=25.12 MB
maxM=3641 MB, totalM=245.5 MB, freeM=209.1 MB, usedM=36.4 MB
maxM=3641 MB, totalM=245.5 MB, freeM=197.82 MB, usedM=47.68 MB
maxM=3641 MB, totalM=245.5 MB, freeM=186.54 MB, usedM=58.96 MB
maxM=3641 MB, totalM=245.5 MB, freeM=233.51 MB, usedM=11.99 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.22 MB, usedM=23.28 MB
maxM=3641 MB, totalM=245.5 MB, freeM=211.58 MB, usedM=33.92 MB
maxM=3641 MB, totalM=245.5 MB, freeM=200.94 MB, usedM=44.56 MB
maxM=3641 MB, totalM=245.5 MB, freeM=190.3 MB, usedM=55.2 MB
maxM=3641 MB, totalM=245.5 MB, freeM=232.87 MB, usedM=12.63 MB
maxM=3641 MB, totalM=245.5 MB, freeM=221.16 MB, usedM=24.34 MB
maxM=3641 MB, totalM=245.5 MB, freeM=209.88 MB, usedM=35.62 MB
maxM=3641 MB, totalM=245.5 MB, freeM=198.6 MB, usedM=46.9 MB
maxM=3641 MB, totalM=245.5 MB, freeM=187.32 MB, usedM=58.18 MB
maxM=3641 MB, totalM=245.5 MB, freeM=233.56 MB, usedM=11.94 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.65 MB, usedM=22.85 MB
maxM=3641 MB, totalM=245.5 MB, freeM=212.01 MB, usedM=33.49 MB
maxM=3641 MB, totalM=245.5 MB, freeM=201.37 MB, usedM=44.13 MB
maxM=3641 MB, totalM=245.5 MB, freeM=190.73 MB, usedM=54.77 MB
maxM=3641 MB, totalM=245.5 MB, freeM=234.2 MB, usedM=11.3 MB
maxM=3641 MB, totalM=245.5 MB, freeM=222.74 MB, usedM=22.76 MB
maxM=3641 MB, totalM=245.5 MB, freeM=211.46 MB, usedM=34.04 MB
maxM=3641 MB, totalM=245.5 MB, freeM=200.18 MB, usedM=45.32 MB
maxM=3641 MB, totalM=245.5 MB, freeM=188.9 MB, usedM=56.6 MB
maxM=3641 MB, totalM=234.5 MB, freeM=222.59 MB, usedM=11.91 MB
maxM=3641 MB, totalM=234.5 MB, freeM=211.23 MB, usedM=23.27 MB
原因分析:
我们看下get和Set
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal.ThreadLocalMap.Entry是啥?
/**
* 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;
}
}
因为继承了WeakRefrence<?>所以是弱引用,每次GC的时候“总会”被回收。
但是如果不remove会不会有问题呢?
会的。比如在“线程池”的场景下,如果每次不提前set值而只是覆盖了initValue(),如果不remove会造成“意外”的结果。