国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

ThreadLocal

2019-11-14 21:05:11
字體:
來源:轉載
供稿:網友
ThreadLocal

ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。

當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。

ThreadLocal的接口方法

ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

  • void set(Object value)

設置當前線程的線程局部變量的值。

  • public Object get()

該方法返回當前線程所對應的線程局部變量。

  • public void remove()

將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。

  • PRotected Object initialValue()

返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變為ThreadLocal <T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

ThreadLocal維護變量

ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本。我們自己就可以提供一個簡單的實現版本:

public class SimpleThreadLocal {    private Map valueMap = Collections.synchronizedMap(new HashMap());    public void set(Object newValue) {        valueMap.put(Thread.currentThread(), newValue);//①鍵為線程對象,值為本線程的變量副本    }    public Object get() {        Thread currentThread = Thread.currentThread();        Object o = valueMap.get(currentThread);//②返回本線程對應的變量        if (o == null && !valueMap.containsKey(currentThread)) {        //③如果在Map中不存在,放到Map中保存起來。            o = initialValue();            valueMap.put(currentThread, o);        }        return o;    }    public void remove() {        valueMap.remove(Thread.currentThread());    }    public Object initialValue() {        return null;    }}

雖然上面代碼ThreadLocal實現版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現思路上是相近的。

一個TheadLocal實例

下面,我們通過一個具體的實例了解一下ThreadLocal的具體使用方法。

public class SequenceNumber {    //①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){        public Integer initialValue(){        return 0;        }    };    //②獲取下一個序列值    public int getNextNum(){        seqNum.set(seqNum.get()+1);        return seqNum.get();    }    public static void main(String[] args)    {        SequenceNumber sn = new SequenceNumber();        //③ 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 SequenceNumber sn;        public TestClient(SequenceNumber sn) {            this.sn = sn;        }        public void run()        {            for (int i = 0; i < 3; i++) {//④每個線程打出3個序列值            System.out.println("thread["+Thread.currentThread().getName()+            "] sn["+sn.getNextNum()+"]");        }    }}

通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產生一組序列號,在③處,我們生成3個TestClient,它們共享同一個SequenceNumber實例。運行以上代碼,在控制臺上輸出以下的結果:

thread[Thread-2] sn[1]thread[Thread-0] sn[1]thread[Thread-1] sn[1]thread[Thread-2] sn[2]thread[Thread-0] sn[2]thread[Thread-1] sn[2]thread[Thread-2] sn[3]thread[Thread-0] sn[3]thread[Thread-1] sn[3]

考察輸出的結果信息,我們發現每個線程所產生的序號雖然都共享同一個SequenceNumber實例,但它們并沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。

ThreadLocal的作用

ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。(注意這里說的只是“一般情況”,如果通過ThreadLocal.set() 到線程中的對象是多線程共享的同一個對象,各個線程中訪問的將是同一個共享對象)。

1.提供了保存對象的方法:每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。

2.避免參數傳遞的方便的對象訪問方式:將一個共用的ThreadLocal靜態實例作為key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作為參數傳遞的麻煩。

理解ThreadLocal中提到的變量副本

“當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本” —— 并不是通過ThreadLocal.set( )實現的,而是每個線程使用“new對象”(或拷貝) 的操作來創建對象副本, 通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象(ThreadLocal實例是作為map的key來使用的)。

如果ThreadLocal.set( )進去的對象是多線程共享的同一個對象,那么ThreadLocal.get( )取得的還是這個共享對象本身 —— 那么ThreadLocal還是有并發訪問問題的!

/*  * 如果ThreadLocal.set()進去的是一個多線程共享對象,那么Thread.get()獲取的還是這個共享對象本身—————并不是該共享對象的副本。  * 假如:其中其中一個線程對這個共享對象內容作了修改,那么將會反映到其它線程獲取的共享對象中————所以說 ThreadLocal還是有并發訪問問題的!  */  public class Test implements Runnable  {      private ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();            private Person person;            public Test(Person person)      {          this.person = person;      }            public static void main(String[] args) throws InterruptedException      {          //多線程共享的對象          Person sharePerson = new Person(110,"Sone");          Test test = new Test(sharePerson);                    System.out.println("sharePerson原始內容:"+sharePerson);                    Thread th = new Thread(test);          th.start();          th.join();                    //通過ThreadLocal獲取對象          Person localPerson = test.getPerson();                    System.out.println("判斷localPerson與sharePerson的引用是否一致:"+(localPerson==localPerson));          System.out.println("sharePerson被改動之后的內容:"+sharePerson);      }            @Override      public void run()      {          String threadName = Thread.currentThread().getName();          System.out.println(threadName+":Get a copy of the variable and change!!!");          Person p = getPerson();          p.setId(741741);          p.setName("Boy");      }            public Person getPerson(){          Person p = (Person)threadLocal.get();          if (p==null)          {              p= this.person;              //set():進去的是多線程共享的對象              threadLocal.set(p);          }          return p;      }
ThreadLocal使用的一般步驟

1、在多線程的類(如ThreadDemo類)中,創建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。

2、在ThreadDemo類中,創建一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,并強制轉換為要應用的類型。

3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。

Code
/** * 學生 */public class Student {    private int age = 0;   //年齡     public int getAge() {        return this.age;    }     public void setAge(int age) {        this.age = age;    }}
/** * 多線程下測試程序 */public class ThreadLocalDemo implements Runnable {    //創建線程局部變量studentLocal,在后面你會發現用來保存Student對象    private final static ThreadLocal studentLocal = new ThreadLocal();     public static void main(String[] agrs) {        ThreadLocalDemo td = new ThreadLocalDemo();        Thread t1 = new Thread(td, "a");        Thread t2 = new Thread(td, "b");        t1.start();        t2.start();    }     public void run() {        accessStudent();    }     /**     * 示例業務方法,用來測試     */    public void accessStudent() {        //獲取當前線程的名字        String currentThreadName = Thread.currentThread().getName();        System.out.println(currentThreadName + " is running!");        //產生一個隨機數并打印        Random random = new Random();        int age = random.nextInt(100);        System.out.println("thread " + currentThreadName + " set age to:" + age);        //獲取一個Student對象,并將隨機數年齡插入到對象屬性中        Student student = getStudent();        student.setAge(age);        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());        try {            Thread.sleep(500);        }        catch (InterruptedException ex) {            ex.printStackTrace();        }        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());    }     protected Student getStudent() {        //獲取本地線程變量并強制轉換為Student類型        Student student = (Student) studentLocal.get();        //線程首次執行此方法的時候,studentLocal.get()肯定為null        if (student == null) {            //創建一個Student對象,并保存到本地線程變量studentLocal中            student = new Student();            studentLocal.set(student);        }        return student;    }}

運行結果:

a is running! thread a set age to:76 b is running! thread b set age to:27 thread a first read age is:76 thread b first read age is:27 thread a second read age is:76 thread b second read age is:27

可以看到a、b兩個線程age在不同時刻打印的值是完全相同的。這個程序通過妙用ThreadLocal,既實現多線程并發,游兼顧數據的安全性。

Code

下面通過這樣一個例子來說明:模擬一個游戲,預先隨機設定一個[1, 10]的整數,然后每個玩家去猜這個數字,每個玩家不知道其他玩家的猜測結果,看誰用最少的次數猜中這個數字。

這個游戲確實比較無聊,不過這里恰好可以把每個玩家作為一個線程,然后用ThreadLocal來記錄玩家猜測的歷史記錄,這樣就很容易理解ThreadLocal的作用。

Judge:用來設定目標數字以及判斷猜測的結果。

Player:每個Player作為一個線程,多個Player并行地去嘗試猜測,猜中時線程終止。

Attempt:具有ThreadLocal字段和猜測動作靜態方法的類,ThreadLocal用于保存猜過的數字。

Record:保存歷史記錄的數據結構,有一個List<Integer>字段。

ThreadLocal為了可以兼容各種類型的數據,實際的內容是再通過set和get操作的對象,詳見Attempt的getRecord()。

運行的時候,每個Player Thread都是去調用Attemp.guess()方法,進而操作同一個ThreadLocal變量history,但卻可以保存每個線程自己的數據,這就是ThreadLocal的作用。

public class ThreadLocalTest {        public static void main(String[] args) {        Judge.prepare();        new Player(1).start();        new Player(2).start();        new Player(3).start();    }    }class Judge {        public static int MAX_VALUE = 10;    private static int targetValue;        public static void prepare() {        Random random = new Random();        targetValue = random.nextInt(MAX_VALUE) + 1;    }        public static boolean judge(int value) {        return value == targetValue;    }    }class Player extends Thread {        private int playerId;        public Player(int playerId) {        this.playerId = playerId;    }        @Override    public void run() {        boolean success = false;        while(!success) {            int value = Attempt.guess(Judge.MAX_VALUE);            success = Judge.judge(value);            System.out.println(String.format("Plyaer %s Attempts %s and %s", playerId, value, success ? " Success" : "Failed"));        }        Attempt.review(String.format("[IFNO] Plyaer %s Completed by ", playerId));    }    }class Attempt {        private static ThreadLocal<Record> history = new ThreadLocal<Record>();        public static int guess(int maxValue) {        Record record = getRecord();        Random random = new Random();        int value = 0;        do {            value = random.nextInt(maxValue) + 1;        } while (record.contains(value));        record.save(value);        return value;    }        public static void review(String info) {        System.out.println(info + getRecord());    }        private static Record getRecord() {        Record record = history.get();        if(record == null) {            record = new Record();            history.set(record);        }        return record;    }    }class Record {        private List<Integer> attemptList = new ArrayList<Integer>();;        public void save(int value) {        attemptList.add(value);    }        public boolean contains(int value) {        return attemptList.contains(value);    }        @Override    public String toString() {        StringBuffer buffer = new StringBuffer();        buffer.append(attemptList.size() + " Times: ");        int count = 1;        for(Integer attempt : attemptList) {            buffer.append(attempt);            if(count < attemptList.size()) {                buffer.append(", ");                count++;            }        }        return buffer.toString();    }    }

運行結果

Plyaer 2 Attempts 8 and FailedPlyaer 3 Attempts 6 and FailedPlyaer 1 Attempts 5 and FailedPlyaer 2 Attempts 7 and  SuccessPlyaer 3 Attempts 9 and FailedPlyaer 1 Attempts 9 and FailedPlyaer 3 Attempts 2 and FailedPlyaer 1 Attempts 2 and Failed[IFNO] Plyaer 2 Completed by 2 Times: 8, 7Plyaer 3 Attempts 4 and FailedPlyaer 1 Attempts 1 and FailedPlyaer 3 Attempts 5 and FailedPlyaer 1 Attempts 3 and FailedPlyaer 3 Attempts 1 and FailedPlyaer 1 Attempts 10 and FailedPlyaer 3 Attempts 8 and FailedPlyaer 1 Attempts 6 and FailedPlyaer 3 Attempts 7 and  SuccessPlyaer 1 Attempts 4 and Failed[IFNO] Plyaer 3 Completed by 8 Times: 6, 9, 2, 4, 5, 1, 8, 7Plyaer 1 Attempts 7 and  Success[IFNO] Plyaer 1 Completed by 9 Times: 5, 9, 2, 1, 3, 10, 6, 4, 7
hreadLocal get()

關于ThreadLocal的原理,可以從其get()方法的實現來看

public class ThreadLocal<T> {    ...    public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }    ...}

執行get()時首先獲取當前的Thread,再獲取Thread中的ThreadLocalMap - t.threadLocals,并以自身為key取出實際的value。于是可以看出,ThreadLocal的變量實際還是保存在Thread中的,容器是一個Map,Thread用到多少ThreadLocal變量,就會有多少以其為key的Entry。

我是天王蓋地虎的分割線

參考:http://gaofeihang.blog.163.com/blog/static/84508285201293071940706/

http://blog.csdn.net/qjyong/article/details/2158097(據說這篇文章有些問題,我只選擇其中一部分參考)

http://lavasoft.blog.51cto.com/62575/51926/


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 石屏县| 密山市| 德阳市| 贵州省| 巴青县| 汽车| 井研县| 伊金霍洛旗| 郸城县| 额济纳旗| 万年县| 洪江市| 安宁市| 甘南县| 莲花县| 博乐市| 邢台县| 彝良县| 桦南县| 汉沽区| 合山市| 石狮市| 娱乐| 汾西县| 华池县| 天台县| 南雄市| 灌云县| 河南省| 崇礼县| 铜川市| 交口县| 澎湖县| 搜索| 额尔古纳市| 德昌县| 晋城| 敦化市| 翁源县| 禹州市| 内乡县|