1. 程式人生 > >ThreadLocal用法詳解和原理

ThreadLocal用法詳解和原理

一、用法

ThreadLocal用於儲存某個執行緒共享變數:對於同一個static ThreadLocal,不同執行緒只能從中get,set,remove自己的變數,而不會影響其他執行緒的變數。

1、ThreadLocal.get: 獲取ThreadLocal中當前執行緒共享變數的值。

2、ThreadLocal.set: 設定ThreadLocal中當前執行緒共享變數的值。

3、ThreadLocal.remove: 移除ThreadLocal中當前執行緒共享變數的值。

4、ThreadLocal.initialValue: ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回此方法值。

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

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

package com.coshaho.reflect;

/**

* ThreadLocal用法

* @author coshaho

*

*/

public class MyThreadLocal

{

private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){

/**

* ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回此方法值

*/

@Override

protected Object initialValue()

{

System.out.println("呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!");

return null;

}

};

public static void main(String[] args)

{

new Thread(new MyIntegerTask("IntegerTask1")).start();

new Thread(new MyStringTask("StringTask1")).start();

new Thread(new MyIntegerTask("IntegerTask2")).start();

new Thread(new MyStringTask("StringTask2")).start();

}

public static class MyIntegerTask implements Runnable

{

private String name;

MyIntegerTask(String name)

{

this.name = name;

}

@Override

public void run()

{

for(int i = 0; i < 5; i++)

{

// ThreadLocal.get方法獲取執行緒變數

if(null == MyThreadLocal.threadLocal.get())

{

// ThreadLocal.et方法設定執行緒變數

MyThreadLocal.threadLocal.set(0);

System.out.println("執行緒" + name + ": 0");

}

else

{

int num = (Integer)MyThreadLocal.threadLocal.get();

MyThreadLocal.threadLocal.set(num + 1);

System.out.println("執行緒" + name + ": " + MyThreadLocal.threadLocal.get());

if(i == 3)

{

MyThreadLocal.threadLocal.remove();

}

}

try

{

Thread.sleep(1000);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

}  

}

}

public static class MyStringTask implements Runnable

{

private String name;

MyStringTask(String name)

{

this.name = name;

}

@Override

public void run()

{

for(int i = 0; i < 5; i++)

{

if(null == MyThreadLocal.threadLocal.get())

{

MyThreadLocal.threadLocal.set("a");

System.out.println("執行緒" + name + ": a");

}

else

{

String str = (String)MyThreadLocal.threadLocal.get();

MyThreadLocal.threadLocal.set(str + "a");

System.out.println("執行緒" + name + ": " + MyThreadLocal.threadLocal.get());

}

try

{

Thread.sleep(800);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

}

}

}

<strong>}

</strong>

執行結果如下:

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

執行緒IntegerTask1: 0

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

執行緒IntegerTask2: 0

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

執行緒StringTask1: a

執行緒StringTask2: a

執行緒StringTask1: aa

執行緒StringTask2: aa

執行緒IntegerTask1: 1

執行緒IntegerTask2: 1

執行緒StringTask1: aaa

執行緒StringTask2: aaa

執行緒IntegerTask2: 2

執行緒IntegerTask1: 2

執行緒StringTask2: aaaa

執行緒StringTask1: aaaa

執行緒IntegerTask2: 3

執行緒IntegerTask1: 3

執行緒StringTask1: aaaaa

執行緒StringTask2: aaaaa

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

執行緒IntegerTask2: 0

呼叫get方法時,當前執行緒共享變數沒有設定,呼叫initialValue獲取預設值!

執行緒IntegerTask1: 0

二、原理

執行緒共享變數快取如下:

Thread.ThreadLocalMap<ThreadLocalObject>;

1、Thread: 當前執行緒,可以通過Thread.currentThread()獲取。

2、ThreadLocal:我們的static ThreadLocal變數。

3、Object: 當前執行緒共享變數。

我們呼叫ThreadLocal.get方法時,實際上是從當前執行緒中獲取ThreadLocalMap<ThreadLocalObject>,然後根據當前ThreadLocal獲取當前執行緒共享變數Object。

ThreadLocal.set,ThreadLocal.remove實際上是同樣的道理。

這種儲存結構的好處:

1、執行緒死去的時候,執行緒共享變數ThreadLocalMap則銷燬。

2、ThreadLocalMap<ThreadLocal,Object>鍵值對數量為ThreadLocal的數量,一般來說ThreadLocal數量很少,相比在ThreadLocal中用Map<Thread, Object>鍵值對儲存執行緒共享變數(Thread數量一般來說比ThreadLocal數量多),效能提高很多。

關於ThreadLocalMap<ThreadLocalObject>弱引用問題:

當執行緒沒有結束,但是ThreadLocal已經被回收,則可能導致執行緒中存在ThreadLocalMap<nullObject>的鍵值對,造成記憶體洩露。(ThreadLocal被回收,ThreadLocal關聯的執行緒共享變數還存在)。

雖然ThreadLocal的get,set方法可以清除ThreadLocalMap中key為null的value,但是get,set方法在記憶體洩露後並不會必然呼叫,所以為了防止此類情況的出現,我們有兩種手段。

1、使用完執行緒共享變數後,顯示呼叫ThreadLocalMap.remove方法清除執行緒共享變數;

2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問題則不存在了。