1. 程式人生 > >Android多執行緒-----併發和同步(ThreadLocal)

Android多執行緒-----併發和同步(ThreadLocal)

一.對ThreadLocal的理解

       很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數,也就是進行資料隔離。

       在看很多部落格的時候,總是有很多作者在描述ThreadLocal的作用是資料隔離,並且是每一個執行緒複製了一份,每個執行緒的訪問的資料都是不受其他執行緒影響的。其實,這句話前半句是對的,ThreadLocal的確是資料的隔離,但是並非資料的複製(準確的說是宣告和初始化的),而是在每一個執行緒中建立一個新的資料物件

,然後每一個執行緒使用的是不一樣的。

       說白了,ThreadLocal就是想在多執行緒環境下去保證成員變數的安全。就是解決對執行緒訪問共享資源時發生衝突的問題,也算是一種同步方式

二、先了解一下ThreadLocal類提供的幾個方法

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

set需要首先獲得當前執行緒物件Thread;

然後取出當前執行緒物件的成員變數ThreadLocalMap;注意,變數是儲存線上程中的,而不是儲存在ThreadLocal變數中

如果ThreadLocalMap存在,那麼進行KEY/VALUE設定,KEY就是ThreadLocal;

如果ThreadLocalMap沒有,那麼建立一個;

說白了,當前執行緒中存在一個Map變數,KEY是ThreadLocal,VALUE是你設定的值。

2、ThreadLocal.get: 獲取ThreadLocal中當前執行緒共享變數的值。每個執行緒的變數副本是儲存在哪裡的?

每個執行緒都有一個這樣的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal物件宣告的變數型別作為引數。這樣,我們所使用的ThreadLocal變數的實際資料,通過get函式取值的時候,就是通過取出Thread中threadLocals引用的map,然後從這個map中根據當前threadLocal作為引數,取出資料。現在,變數的副本從哪裡取出來的已經確認解決了。

3、ThreadLocal.remove: 移除ThreadLocal中當前執行緒共享變數的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回此方法值。

我們先來看一個例子

執行結果

是你想象中的結果麼?

很顯然,在這裡,並沒有通過ThreadLocal達到執行緒隔離的機制,可是ThreadLocal不是保證執行緒安全的麼?這是什麼鬼?

雖然,ThreadLocal讓訪問某個變數的執行緒都擁有自己的區域性變數,但是如果這個區域性變數都指向同一個物件呢?這個時候ThreadLocal就失效了。仔細觀察下圖中的程式碼,你會發現,threadLocal在初始化時返回的都是同一個物件a!
 

再來看一個例子

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方法時,當前執行緒共享變數沒有設定,獲取預設值!");

            return null;

        }

    };

     

    public static void main(String[] args)

    {

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

        new Thread(new MyIntegerTask("IntegerTask2")).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();

                }

            }  

        }

         

    }

執行結果如下:

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

執行緒IntegerTask1: 0

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

執行緒IntegerTask2: 0

執行緒IntegerTask1: 1

執行緒IntegerTask2: 1

執行緒IntegerTask2: 2

執行緒IntegerTask1: 2

執行緒IntegerTask2: 3

執行緒IntegerTask1: 3

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

執行緒IntegerTask2: 0

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

執行緒IntegerTask1: 0

 

三.ThreadLocal與其他同步機制的區別

       ThreadLocal與普通的同步機制都是為了解決多執行緒訪問共享資源時會產生衝突的問題,
       普通的同步機制是控制了執行緒對共享資源的訪問時間而避免衝突的,他是多個執行緒進行通訊的有效方式,
       而ThreadLocal則是在空間上對共享資料進行了隔離,從根本上來說,資料已經不在共享了以此避免衝突。因此兩種方式是在不同的角度所實現的執行緒安全。
       當我們需要多執行緒之間進行通訊就使用同步機制,需要隔離多個執行緒之間的共相沖突,就是用ThreadLocal。

四.ThreadLocal的應用場景

  最常見的ThreadLocal使用場景為 用來解決 資料庫連線、Session管理等。

 

感謝:
http://www.importnew.com/20963.html
http://www.cnblogs.com/dolphin0520/p/3920407.html
https://www.cnblogs.com/coshaho/p/5127135.html