java-ThreadLocal使用

ThreadLocal能用来做什么

保证在ThreadLocal中保存的对象,在同一个线程中获取到的是同一个实例。
如:spring-Transaction中的connetcion对象,hibernate-session对象及mybatis-session对象。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.IOException;

public class ThreadLocalTest {

private static ThreadLocal<D> threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws IOException {

D d = new D();

threadLocal.set(d);
System.out.println(d==threadLocal.get());//返回ture,说明是同一个对象

Thread thread = new Thread(){
@Override
public void run() {
System.out.println(threadLocal.get());// null,说明其他线程是获取不到的啊
}
};
thread.start();

//不remove的话会导致内存泄露
System.out.println(threadLocal.get());
//threadLocal.remove();

System.in.read();
}

}

class D{
private int id;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}

总结:同一个线程中获取到的是同一个对象,其他线程获取不到。
不使用的对象要调用remove方法删除掉。

源码分析

java.lang.ThreadLocal#set方法

1
2
3
4
5
6
7
8
9
10
11
12
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

调用向ThreadLocal set(value)时,是将value放入当前线程对象的currentThread#
threadLocals(ThreadLocal.ThreadLocalMap类型)变量中了。

接着看ThreadLocalMap#set方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static class ThreadLocalMap {

static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

private void set(ThreadLocal<?> key, Object value) {
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();
}
}

以Entry<threadLocal对象, value>方式放进ThreadLocalMap。

对象引用

强引用(普通new对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class D{
private int id;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Override
protected void finalize() throws Throwable {
System.out.println("finalize");//输出这个说明垃圾回收器回收了该对象
super.finalize();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class T1_CommonReferenceTest {

public static void main(String[] args) {
// 1.正常new对象并使用
D d = new D();
System.out.println(d);

// 2.等于null时,该对象没有引用了
//表示可以被gc回收,但需要等待gc运行才真正被回收
d = null;

//3.手动运行gc,一般情况下是不需要的
System.gc();

//4.检查改对象是否为null
System.out.println(d);
}

}

总结:d对象一直会存在,只有当对象引用被删除时,gc才会回收空间

SoftReference软引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.ref.SoftReference;

public class T2_SoftReferenceTest {

// 配置JVM最大堆内存为20M
// -Xms20M -Xmx20M
public static void main(String[] args) {

//1.生成一个对象,占用内存10M
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
//2.对象还存在,没有被回收
System.out.println(softReference.get());

//这里又生成了个大对象
byte[] bytes2 = new byte[1024*1024*10];
System.out.println(bytes2);
//3.对象被回收了
System.out.println(softReference.get());//null

}

}

总结:当堆空间不足时,gc会自动回收软引用对象;
所以,SoftReference对象适合来做缓存。

WeakReference弱引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.ref.WeakReference;

public class T3_WeakReferenceTest {

public static void main(String[] args) {
WeakReference<D> weakReference = new WeakReference<>(new D());
//1.获取到内存对象
System.out.println(weakReference.get());
System.gc();//2.运行gc
//3.gc运行以后为空,说明空间已经被垃圾回收器释放了
System.out.println(weakReference.get());

//4.继续使用该对象的话则不会被清除
//System.out.println(dInstance.getId());
}

}

总结:gc会自动回收不再使用的弱引用对象。
对比ThreadLocal中,如果不是弱引用对象的话,可能会导致内存泄露。

PhantomReference虚引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class T4_PhantomReferenceTest {

public static void main(String[] args) throws IOException {
//1.观察引用队列
ReferenceQueue<D> referenceQueue = new ReferenceQueue<>();
PhantomReference<D> phantomReference = new PhantomReference<D>(new D(), referenceQueue);

// 2.
new Thread(){
@Override
public void run() {
while (true){
if(referenceQueue.poll()!=null){
System.out.println("1111");
}
}
}
}.start();

new Thread(){
@Override
public void run() {
while (true){
System.out.println(phantomReference.get());//null,获取不到
}
}
}.start();


//使用输入流,阻塞住main线程,否则观察不到
System.in.read();

}

}

总结:phantomReference.get()是一致为空的,说明在jvm内存中是一直获取不到的。
作用:用来管理堆外内存的。
如NIO中DirectByteBuffer对象,使用的是堆外内存;
由专门的gc线程负责监视ReferenceQueue并销毁。

内存映射文件与DirectMemory