Java多线程-ThreadLocal

ThreadLocal源码解读

基本参数

1
2
3
4
5
6
7
// 标识每一个ThreadLocal的唯一性
private final int threadLocalHashCode = nextHashCode();
// 自增哈希值
private static AtomicInteger nextHashCode =
new AtomicInteger();
// 增量
private static final int HASH_INCREMENT = 0x61c88647;

构造函数

  • 无参构造函数
1
2
3
4
5
6
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}

get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取线程变量的副本
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
// getMap方法从线程t中获取threadLocalMap,threadLocalMap是线程的一个成员变量threadLocals,每一个线程有一个自己的ThreadLocalMap对象来维护自己的变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 第二次调用map获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 第一次调用get方法会调用setInitialValue方法
return setInitialValue();
}

getMap返回一个ThreadLocalMap类型的值

1
2
3
4
5
6
7
8
9
10
/**
* 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;
}

实际上第一次调用,map为空,调用setInitialValue()方法来初始化map

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
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
// 实际上初始化map的地方,需要重写initialvalue方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 调用createMap来创建map
createMap(t, value);
return value;
}
/**
* 重写之后的返回值作为ThreadLocal的value值
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}

createMap方法实际上完成ThreadLcoalMap的初始化,每个Thread都有一个ThreadLocal.ThreadLocalMap。其中key为ThreadLocal这个实例,value为每次initialValue()得到的变量。

1
2
3
4
5
6
7
8
9
10
11
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
// 创建ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

第二次调用get方法,会进入if里面,调用getEntry方法获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 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);
}

set方法

1
2
3
4
5
6
7
8
9
10
11
12
public void set(T value) {
// 取得当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// map不为空,直接传值
map.set(this, value);
else
// map为空,则创建map并将初始值传入
createMap(t, value);
}

remove方法

1
2
3
4
5
6
7
8
9
10
11
/**
* Removes the current thread's value for this thread-local
* variable.
*/
public void remove() {
// 获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 移除当前实例的ThreadLocal
m.remove(this);
}

总结

  1. 每个线程都有自己的局部变量,一个线程的本地变量对于其他线程是不可见的
  2. 独立于变量的初始化副本,ThreadLocal可以给一个初始值,而每个线程都会获得到这个初始化值的一个副本,这样能保证不同的线程都有一份拷贝
  3. 状态与某一个线程相关联,ThreadLocal不是用于解决共享变量的问题的,不是为了协调线程同步而存在的,而是为了方便每个线程处理自己的状态而引入的一个机制。

ThreadLocal实例

  • 格式化时间
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
package third;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalDemo {
//SimpleDateFormat线程安全
// private static final SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final ThreadLocal<SimpleDateFormat> t1= new ThreadLocal<>();
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
// Date t = sdf.parse("2015-03-29 19:59:" + i%60);
if (t1.get() == null)
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
Date t = t1.get().parse("2015-03-29 19:59:" + i%60);
System.out.println(i+ ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService ex = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
ex.execute(new ParseDate(i));
}
}
}
  • Gc演示
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package third;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
protected void finalize() throws Throwable {
System.out.println(this.toString() + "is gc");
}
};
static volatile CountDownLatch cd = new CountDownLatch(10000);
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (t1.get() == null) {
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
protected void finalize() throws Throwable {
System.out.println(this.toString() + "is gc");
}
});
System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
}
Date t = t1.get().parse("2015-03-29 19:29:" + i % 60);
} catch (ParseException e) {
e.printStackTrace();
} finally {
cd.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();
System.out.println("mission complete!!");
t1 = null;
System.gc();
System.out.println("first GC compile!!");
t1 = new ThreadLocal<SimpleDateFormat>();
cd = new CountDownLatch(10000);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();
Thread.sleep(1000);
System.gc();
System.out.println("Second GC compile!");
}
}
Donate comment here