1. 程式人生 > >Java基礎加強之多執行緒篇 - 執行緒建立與終止、互斥、通訊、本地變數

Java基礎加強之多執行緒篇 - 執行緒建立與終止、互斥、通訊、本地變數

執行緒建立與終止

執行緒建立

Thread類與 Runnable 介面的關係

public interface Runnable {
        public abstract void run();
}

public class Thread implements Runnable {
    /* What will be run. */
        private Runnable target;
        ......
        /**
         * Causes this thread to begin execution; the Java Virtual Machine
         * calls the <code>run</code> method of this thread.
         */
        public synchronized void start() {......}

        ......
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
        }
        ......
}

Thread類與 Runnable介面 都位於java.lang包中。 從上面我們可以看出,Runnable介面中只定義了run()方法,Thread類實現了Runnable 介面並重寫了run()方法。 當呼叫Thread 類的 start()方法時,實際上Java虛擬機器就去呼叫Thread 類的 run()方法 ,而 Thread 類的 run()方法 中最終呼叫的是 Runnable 型別物件的run()方法 。

繼承 Thread並重寫 run 方法

public class ThreadTest1 extends Thread {
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread 1:" + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        ThreadTest1 thread = new ThreadTest1 ();
        thread.start();
    }//main end
}

可以寫成內部類的形式, new Thread(){ @Override run(…) }.start();

實現 Runnable介面並重寫 run 方法

public class ThreadTest2  implements Runnable {
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread 3:" + Thread.currentThread().getName());
        }
    }
    public static void main(String[] args) {
        ThreadTest2  thread3 = new ThreadTest2();
        Thread thread = new Thread(thread3);
        thread.start();
    }//main end
}

可以寫成內部類的形式, new Thread( new Runnable(){@Override run(…)} ).start();

執行緒終止

當呼叫 Thread類的 start() 方法時,將會建立一個執行緒,這時剛建立的執行緒處於就緒狀態(可執行狀態),並沒有執行,處於就緒狀態的執行緒就可以等 JVM 排程。當 JVM 排程該執行緒時,該執行緒進入執行狀態,即執行 Thread 類的 run() 方法中的內容。 run() 方法執行完,執行緒結束,執行緒進入死亡狀態。這是執行緒自然終止的過程,我們也可以通過 Thread 類提供的一些方法來終止執行緒。

interrupt()\isInterrupted()\interrupted() 方法介紹
stop() 方法沒有做任何的清除操作就粗暴終止執行緒,釋放該執行緒所持有的物件鎖(下文將介紹),受該物件鎖保護的其它物件對其他執行緒可見,因此具有不安全性。

suspend() 方法會使目標執行緒會停下來,但仍然持有在這之前獲得的物件鎖,對任何執行緒來說,如果它們想恢復目標執行緒,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。

終上所述, 不建議使用stop()方法和 suspend() 方法來終止執行緒,通常我們通過 interrupt() 方法來終止處於阻塞狀態和執行狀態的執行緒 。

需要注意的是, interrupt()方法不會中斷一個正在執行的執行緒,僅僅是將執行緒的中斷標記設為 true ,當呼叫了阻塞方法之後,執行緒會不斷監聽中斷標誌,如果為true,則產生一個 InterruptedException 異常,將 InterruptedException 放在 catch 中就能終止執行緒。

isInterrupted()方法可以返回中斷標記 ,常用迴圈判斷條件。

interrupted()方法測試當前執行緒是否已經中斷,執行緒的中斷標誌由該方法清除。 interrupted()除了返回中斷標記之外,它還會清除中斷標記 。

interrupt() 用法
看下面例子

public class ThreadInterruptedTest extends Thread {
    @Override
    public void run() {
            try {
                int i = 0;
                while(!isInterrupted()) {
                    i ++ ;
                    Thread.sleep(1000);
                    System.out.println(this.getName() + " is looping,i=" + i);
                }
            } catch (InterruptedException e) {
                System.out.println(this.getName() + 
                        " catch InterruptedException,state:" + this.getState());  
                e.printStackTrace();
            }
    }

    public static void main(String[] args) throws Exception {
        
        ThreadInterruptedTest thread = new ThreadInterruptedTest();
        System.out.println(thread.getName() 
                + " state:" + thread.getState());  
        
        thread.start();
        System.out.println(thread.getName() 
                + " state:" + thread.getState());  
        
        Thread.sleep(5000);
        
        System.out.println("flag: " + thread.isInterrupted());
        
        //發出中斷指令
        thread.interrupt();
        
        System.out.println("flag: " + thread.isInterrupted());
        
        System.out.println(thread.getName() 
                + " state:" + thread.getState());  
        
        System.out.println(thread.interrupted());
    }
}

執行結果

Thread-0 state:NEW
Thread-0 state:RUNNABLE
Thread-0 is looping,i=1
Thread-0 is looping,i=2
Thread-0 is looping,i=3
Thread-0 is looping,i=4
flag: false
flag: true
Thread-0 state:TIMED_WAITING
Thread-0 catch InterruptedException,state:RUNNABLE
false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.itpsc.thread.ThreadInterruptedTest.run(ThreadInterruptedTest.java:11)

從執行結果可以看出,呼叫 interrupt() 發出中斷指令前,中斷標誌位 false ,發出中斷指令後中斷標誌位為 true ,而呼叫 interrupted() 方法後則中斷標誌被清除。從發出的異常來看,是在一個 sleep interrupted ,且發出異常後執行緒被喚醒,以便執行緒能從異常中正常退出。

執行緒執行狀態圖

執行緒從建立到終止可能會經歷各種狀態。在 . . .State類的原始碼中,可以看到執行緒有以下幾種狀態: NEW 、 RUNNABLE 、 BLOCKED 、 WAITING 、 TIMED_WAITING 、 TERMINATED 。各種狀態的轉換如下:在這裡插入圖片描述
當通過 Thread t = new Thread()方式建立執行緒時,執行緒處於新建狀態;當呼叫 t.start() 方法時,執行緒進入可執行狀態(注意,還沒有執行);處於可執行狀態的執行緒將在適當的時機被 CPU 資源排程器排程,進入執行狀態,也就是執行緒執行 run() 方法中的內容; run() 方法執行完或者程式異常退出執行緒進入終止狀態。執行緒從執行狀態也有可能進入阻塞狀態,如呼叫 wait() 方法後進入等待物件鎖(下文將介紹),呼叫 sleep() 方法後進行入計時等待。

執行緒互斥

現在我們已經知道執行緒的建立與終止了。互斥,是指系統中的某些共享資源,一次只允許一個執行緒訪問,當一個執行緒正在訪問該臨界資源時,其它執行緒必須等待。

物件鎖

在 java中, 每一個物件有且僅有一個鎖,鎖也稱為物件監視器 。通過物件的鎖,多個執行緒之間可以實現對某個方法(臨界資源)的互斥訪問。那麼,如何獲取物件的鎖呢?當我們 呼叫物件的synchronized修飾的方法或者 synchronized 修飾的程式碼塊時,鎖住的是物件例項,就獲取了該物件的鎖 。

全域性鎖

Java中有例項物件也有類物件,竟然有物件鎖,那麼久有類鎖,也稱 全域性鎖 。 當synchronized修飾靜態方法或者靜態程式碼塊時,鎖住的是該類的 Class 例項(位元組碼物件),獲取的便是該類的全域性鎖 。看下面獲取物件鎖實現執行緒互斥的兩種方式。

執行緒互斥的兩種方式

先看下面這個沒有實現執行緒互斥的例子。

public class SynchronizedTest {
    public static void main(String[] args) {
        new SynchronizedTest().init();
    }    
    private void init() {
        final Outputer output = new Outputer();
        //執行緒1列印"hello,i am thread 1"
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true) {
                     try{
                         Thread.sleep(1000);
                     }catch(InterruptedException e) {
                         e.printStackTrace();
                     }
                     output.output("hello,i am thread 1");
                }    
            }
        }).start();            
        //執行緒2列印"hello,i am thread 2"
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true) {
                     try{
                         Thread.sleep(1000);
                     }catch(InterruptedException e) {
                         e.printStackTrace();
                     }
                     output.output("hello,i am thread 2");
                }
            }
        }).start();
    }        
    class Outputer {
        public void output(String name) {
            for(int i=0; i<name.length(); i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }
    }
}

執行結果

hello,i am thread 1
hello,i am thread 2
hello,i am hellthread 1
o,i am thread 2
hello,i am thread 2
hello,i am thread 1
hello,i am thread 2
hello,i am threadhel 2lo,i am thread 
1

執行緒 1和執行緒2同時呼叫 進行輸出,從執行結果可以看出,執行緒之間沒有執行完各自的輸出任務就被交替了運行了 。下面通過物件的鎖實現執行緒1和執行緒2對output方法的互斥訪問。

synchronized 修飾方法
使用 synchronized 對 output 方法進行修飾,可以讓呼叫者獲得鎖。 synchronized 修飾方法沒有顯示宣告鎖的物件,預設是當前方法所在類的物件 this 。

public synchronized void output(String name) {
    for(int i=0; i<name.length(); i++) {
        System.out.print(name.charAt(i));
    }
    System.out.println();
}  

synchronized 修飾程式碼塊
使用 synchronized 對 output 方法中的程式碼塊進行修飾,也可以讓呼叫者獲得鎖。

public void output(String name) {
    synchronized(this){
        for(int i=0; i<name.length(); i++) {
            System.out.print(name.charAt(i));
        }
        System.out.println();
    }
} 

使用 synchronized之後,執行緒 1 和執行緒 2 對 output 方法實現了互斥訪問。

hello,i am thread 1
hello,i am thread 2
hello,i am thread 1
hello,i am thread 2
hello,i am thread 1
hello,i am thread 2
hello,i am thread 1

synchronized 用法

先看下面的例子,我們來總結下 synchronized 的一些常用用法。

public class SynchronizedTest {
    public static void main(String[] args) {
        new SynchronizedTest().init();
    }    
    private void init() {
        final Outputer output = new Outputer();
        //執行緒1列印"hello,i am thread 1"
        new Thread(new Runnable(){
            @Override
            public void run() {
                output.output("hello,i am thread 1");
            }
        }).start();        
        //執行緒2列印"hello,i am thread 2"
        new Thread(new Runnable(){
            @Override
            public void run() {
                output.output("hello,i am thread 2");
            }
        }).start();
    }    
    static class Outputer {
        public synchronized void output(String name) {
            for(int i=0; i<5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name);
            }
        }            
        public void output2(String name) {
            synchronized(this) {
                for(int i=0; i<5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name);
                }
            }
        }            
        public void output3(String name) {
            for(int i=0; i<5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name);
            }
        }            
        public static synchronized void output4(String name) {
            for(int i=0; i<5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name);
            }
        }            
        public void output5(String name) {
            synchronized(Outputer.class) {
                for(int i=0; i<5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name);
                }
            }
        }
    }
}

執行結果

hello,i am thread 1
hello,i am thread 1
hello,i am thread 1
hello,i am thread 1
hello,i am thread 1
hello,i am thread 2
hello,i am thread 2
hello,i am thread 2
hello,i am thread 2
hello,i am thread 2

執行緒 1和執行緒 2 同時訪問 output 物件的 synchronized 修飾的 output 方法,即兩個執行緒競爭的是 output 物件的鎖,這是同一個鎖,所以當執行緒 1 在持有鎖的時候,執行緒 2 必須等待,即下面的用法 1 。

用法 1

當一個執行緒訪問某個物件的 synchronized 方法或者 synchronized 程式碼塊時, 其它執行緒 對該物件的 該synchronized 方法或者 synchronized 程式碼塊 的訪問將阻塞。

用法 2

當一個執行緒訪問某個物件的 synchronized 方法或者 synchronized 程式碼塊時, 其它執行緒 對該物件的 其他synchronized 方法或者 synchronized 程式碼塊 的訪問將阻塞。

修該上面的 SynchronizedTest 例子,執行緒 1 訪問 output 方法,執行緒 2 訪問 output2 方法,執行結果同上,因為 output 方法 和 output2 方法都屬於同一個物件 output ,因此執行緒 1 和執行緒 2 競爭的也是同一個鎖。

用法 3

當一個執行緒訪問某個物件的 synchronized 方法或者 synchronized 程式碼塊時, 其它執行緒仍然可以 對該物件的 其他非synchronized 方法或者 synchronized 程式碼塊 訪問。

修該上面的 SynchronizedTest 例子,執行緒 1 訪問 output 方法,執行緒 2 訪問 output3 方法,執行結果是執行緒 1 和執行緒 2 交替輸出。結果顯而易見,執行緒 2 訪問 output3 方法並不是 synchronized 修飾的 output 方法或者程式碼塊,執行緒 2 並不需要持有鎖,因此執行緒 1 的執行不會阻塞執行緒 2 的執行。

用法 4

當 synchronized 修飾靜態方法時,鎖住的是該類的 Class 例項(位元組碼物件)。修該上面的 SynchronizedTest 例子,執行緒 1 訪問 output4 方法,執行緒 2 訪問 output5 方法,執行結果同用法 1 ,說明執行緒 1 和執行緒 2 競爭的是 Outputer 類的 Class 例項(位元組碼物件)的鎖。

執行緒通訊

多個執行緒之間往往需要相互協作來完成某一個任務, synchronized 和物件鎖能實現執行緒互斥,但是不能實現執行緒通訊 。

wait()\notify()\notifyAll() 介紹

執行緒之間的通訊通過 java.lang 包中Object類中的 wait()方法和notify()、notifyAll()等方法進行。我們知道, Java 中 每個物件都有一個鎖 , wait() 方法用於等待物件的鎖, notify()、notifyAll() 方法用於通知其他執行緒物件鎖可以使用。

wait()\notify()\notifyAll()依賴於物件鎖,物件鎖是物件所持有,Object類是所有java類的父類,這樣每一個java類(物件)都有執行緒通訊的基本方法 。這就是這些方法定義在Object類中而不定義在Thread類中的原因。

wait()方法的會讓當前執行緒釋放物件鎖並進入等待物件鎖的狀態,當前執行緒是指正在cpu上執行的執行緒。當前執行緒呼叫notify()\notifyAll()後,等待物件鎖的執行緒將被喚醒。

呼叫 wait()方法或者notify()方法的物件必須和物件鎖所屬的物件是同一個物件,並且必須在synchronized方法或者synchronized程式碼塊中被呼叫。

yieId() 介紹

yieId()的作用是給執行緒排程器一個提示,告知執行緒排程器當前執行緒願意讓出 CPU ,但是執行緒排程器可以忽略這個提示。因此, yieId()的作用僅僅是告知執行緒排程器當前執行緒願意讓出 CPU 給其他執行緒執行(竟然只是願意,當前執行緒可以隨時反悔,那其他執行緒也不一定能得到 CPU 執行),而且不會讓當前執行緒釋放物件鎖 。

yieId()能讓當前執行緒由執行狀態進入到就緒狀態,從而讓其它具有相同優先順序的等待執行緒獲取執行權。但是,並不能保證在當前執行緒呼叫 yield() 之後,其它具有相同優先順序的執行緒就一定能獲得執行權,也有可能當前執行緒又進入到執行狀態繼續執行。

yieId() 只建議在測試環境中使用。

wait()和 yield() 的區別

( 1) wait() 是讓執行緒由執行狀態進入到等待 ( 阻塞 ) 狀態,而 yield() 是讓執行緒由執行狀態進入到就緒狀態。

( 2) wait() 是讓執行緒釋放它所持有物件的鎖,而 yield() 方法不會釋放鎖。

多執行緒交替輸出及 volatile 應用
下面的例子是 “主執行緒輸出三次接著子執行緒輸出三次”,重複兩次。

public class WaitnotifyTest {
    public static volatile boolean shouldChildren = false;
    public static void main(String[] args) throws Exception{
        final Outputer outputer = new Outputer();
        //建立子執行緒
        Thread chrild = new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    for(int i=0;i<2;i++)
                        outputer.children();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        chrild.start();
        //主執行緒
        for(int i=0;i<2;i++)
            outputer.main();
    }
}


class Outputer {
    //子執行緒迴圈輸出
    public synchronized void children() throws Exception{
        while(!WaitnotifyTest.shouldChildren) {
            System.out.println(Thread.currentThread().getName()
                    + " thread end loop,go to waitting");
            //子執行緒進入等待狀態
            this.wait();
        }
        
        System.out.println(Thread.currentThread().getName()
                + " thread start loop");
        for(int i=1; i<=3; i++) {
            System.out.println("hello,i am chrildren thread,loop:" + i);
        }
        
        WaitnotifyTest.shouldChildren = false;
        //喚醒主執行緒
        this.notify();
    }
    
    //主執行緒迴圈輸出
    public synchronized void main() throws Exception{
        while(WaitnotifyTest.shouldChildren) {
            System.out.println(Thread.currentThread().getName()
                    + " thread end loop,go to waitting");
            //主執行緒進入等待狀態
            this.wait();
        }
        
        System.out.println(Thread.currentThread().getName()
                + " thread start loop");
        for(int i=1; i<=3; i++) {
            System.out.println("hello,i am main thread,loop:" + i);
        }
        
        WaitnotifyTest.shouldChildren = true;
        //喚醒子執行緒
        this.notify();
    }
}

執行結果

main thread start loop
hello,i am main thread,loop:1
hello,i am main thread,loop:2
hello,i am main thread,loop:3
main thread end loop,go to waitting
Thread-0 thread start loop
hello,i am chrildren thread,loop:1
hello,i am chrildren thread,loop:2
hello,i am chrildren thread,loop:3
Thread-0 thread end loop,go to waitting
main thread start loop
hello,i am main thread,loop:1
hello,i am main thread,loop:2
hello,i am main thread,loop:3
Thread-0 thread start loop
hello,i am chrildren thread,loop:1
hello,i am chrildren thread,loop:2
hello,i am chrildren thread,loop:3

volatile修飾 shouldChildren,執行緒直接讀取shouldChildren變數並且不快取它,修改了shouldChildren 立馬讓其他執行緒可見,這就確保執行緒讀取到的變數是一致的。

執行緒本地變數

執行緒本地變數

執行緒本地變數,可能稱為 執行緒區域性變數 更容易理解,即為 每一個使用該變數的執行緒都提供一個變數值的副本 ,相當於將變數的副本繫結到執行緒中,每一個執行緒可以獨立地修改自己的變數副本,而不會和其它執行緒的變數副本衝突。 線上程消失之後,執行緒區域性變數的所有副本都會被垃圾回收 (下面的原始碼分析中將提到) 。

ThreadLocal 實現分析

ThreadLocal

在 java.lang.Thread類中,有一個 ThreadLocal.ThreadLocalMap型別的變數 threadLocals ,這個變數就是用來儲存執行緒區域性變數 的。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; 

下面我們重點分析 ThreadLocal的內部實現。 ThreadLocal 也位於 java.lang 包中。其主要成員有:

public T get() {}
private T setInitialValue() {}
public void set(T value) {}
private void remove(ThreadLocal key) {}
ThreadLocalMap getMap(Thread t){}
void createMap(Thread t, T firstValue) {}
static class ThreadLocalMap {} 

Set

我們從 set方法開始。 Set 方法原始碼如下

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}  

先獲取當前的執行緒,然後通過 getMap(t)方法獲取到一個 map , map 的型別為 ThreadLocalMap 。

這個 map其實就是儲存執行緒變數的物件 threadLocals 。 ThreadLocalMap是 ThreadLocal 中的一個內部類,是一個定製的 hashmap 以便適用於儲存執行緒本地變數 。竟然是定製的hashmap,那麼就有 Entry 和 table ( hashmap 的內部實現參考上一篇: Java基礎加強之集合篇(模組記憶、精要分析) )。而 ThreadLocalMap中的 Entry 繼承了 WeakReference ,弱引用是不能保證不被垃圾回收器回收的 ,這就是前文提到的線上程消失之後,執行緒區域性變數的所有副本都會被垃圾回收。此外,Entry 中使用 ThreadLocal 作為 key ,執行緒區域性變數作為 value 。如果 threadLocals 不為空,則設值否者呼叫 createMap 方法建立 threadLocals 。 注意設值的時候傳的是this而不是當前執行緒 t 。

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

接下來我們看看 createMap 方法

/**
 * 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
 * @param map the map to store.
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
} 

createMap方法其實就是為當前執行緒的 threadLocals 變數分配空間並存儲執行緒的第一個變數。現在我們已經知道執行緒是如何初始化並設值自己的區域性變量了,下面我們看看取值。

Get

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
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();
}  

先獲取當前的執行緒,然後通過 getMap(t)方法獲取當前執行緒存變數的物件 threadLocals ,如果 threadLocals 不為空則取值並返回( 注意傳入的key是 this 物件而不是當前執行緒 t ),否則呼叫setInitialValue方法初始化。 setInitialValue 和 set 方法唯一不同的是呼叫了 initialValue 進行初始化,也就是在獲取變數之前要初始化。

/**
 * 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() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}  

總的來講,每建立一個執行緒( Thread物件),該執行緒即擁有儲存執行緒本地變數的 threadLocals 物件, threadLocals 物件初始為 null ,當通過 ThreadLocal 物件呼叫 set/get 方法時,就會對執行緒的 threadLocals 物件進行初始化,並且以當前 ThreadLocal 物件為鍵值,以 ThreadLocal 要儲存的變數為 value ,存到 threadLocals 。看下面的例子。

ThreadLocal 應用

public class ThreadLocalShareVariable {    
    public static void main(String[] args) {
        //建立3個執行緒
        for(int i=0; i<3;i++) {
            //建立執行緒
            new Thread(new Runnable(){
                @Override
                public void run() {
                    //執行緒設定自己的變數
                    int age = new Random().nextInt(100);
                    String name = getRandomString(5);
                    System.out.println("Thread " + Thread.currentThread().getName() 
                            + " has put data:" + name + " " + age);
                    
                    //儲存與當前執行緒有關的變數
                    Passenger.getInstance().setName(name);
                    Passenger.getInstance().setAge(age);
                    
                    //執行緒訪問共享變數
                    new ModuleA().getData();
                    new ModuleB().getData();
                }
            }).start();
        }
    }
    
    static class ModuleA {
        public void getData(){
            //獲取與當前執行緒有關的變數
            String name = Passenger.getInstance().getName();
            int data = Passenger.getInstance().getAge();
            System.out.println("moduleA get data from " 
            + Thread.currentThread().getName() + ":" + name + " "+ data);
        }
    }
    
    static class ModuleB {
        public void getData(){
            //獲取與當前執行緒有關的變數
            String name = Passenger.getInstance().getName();
            int data = Passenger.getInstance().getAge();
            System.out.println("moduleB get data from " 
            + Thread.currentThread().getName() + ":" + name + " "+ data);
        }
    }
    
    /**
     * 隨機生成字串
     * @param length
     * @return
     */
    public static String getRandomString(int length){
        final String str = "abcdefghijklmnopqrstuvwxyz"; 
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < length; i++) {
            sb.append(str.charAt(
                    (int) Math.round(Math.random() * (len-1))));
        }
        return sb.toString();
    }

}

class Passenger {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Passenger(){}
    
    //ThreadLocal儲存執行緒變數
    public static ThreadLocal<Passenger> thsd = new ThreadLocal<Passenger>();
    
    public static Passenger getInstance() {
        //獲取當前執行緒範圍內的共享變數例項
        Passenger passenger = thsd.get();
        //懶漢模式建立例項
        if(passenger == null) {
            passenger = new Passenger();
            thsd.set(passenger);
        }
        return passenger;
    }
    
}

View Code

執行結果

Thread Thread-1 has put data:vwozg 33
Thread Thread-2 has put data:hubdn 30
Thread Thread-0 has put data:mkwrt 35
moduleA get data from Thread-2:hubdn 30
moduleA get data from Thread-0:mkwrt 35
moduleA get data from Thread-1:vwozg 33
moduleB get data from Thread-1:vwozg 33
moduleB get data from Thread-0:mkwrt 35
moduleB get data from Thread-2:hubdn 30

View Code

建立 3個執行緒,每個執行緒要儲存一個 Passenger 物件,並且通過 ModuleA 、 ModuleB 來訪問每個執行緒對應儲存的 Passenger 物件。

多執行緒之間共享變數

上面我們討論的是多執行緒之間如何訪問自己的變數。那麼多執行緒之間共享變數時如何的呢,看下的例子,執行緒 1對共享變數進行減一操作,執行緒 2 對共享變數進行加 2 操作。

public class MutilThreadShareVariable {
    static volatile int count = 100;
    public static void main(String[] args) throws Exception{
        final ShareDataDec sdDec = new ShareDataDec();
        final ShareDataInc sdInc = new ShareDataInc();
        //執行緒1
        new Thread(new Runnable() {
            @Override
            public void run() { 
                for(int i=0;i<5;i++) {
                    sdDec.dec();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        //執行緒2
        new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i=0;i<5;i++) {
                    sdInc.inc();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();;
    }
    
    static class ShareDataDec {
        public synchronized void dec() {
            count --;
            System.out.println("Thread " + Thread.currentThread().getName() 
                    + " dec 1 from count,count remain " + count);
        }
    }
    
    static class ShareDataInc {
        public synchronized void inc() {
            count = count + 2;
            System.out.println("Thread " + Thread.currentThread().getName() 
                    + " inc 2 from count,count remain " + count);
        }
    }
}

View Code  

執行結果

Thread Thread-0 dec 1 from count,count remain 99
Thread Thread-1 inc 2 from count,count remain 101
Thread Thread-0 dec 1 from count,count remain 100
Thread Thread-1 inc 2 from count,count remain 102
Thread Thread-0 dec 1 from count,count remain 101
Thread Thread-1 inc 2 from count,count remain 103
Thread Thread-0 dec 1 from count,count remain 102
Thread Thread-1 inc 2 from count,count remain 104
Thread Thread-0 dec 1 from count,count remain 103
Thread Thread-1 inc 2 from count,count remain 105

View Code

執行緒共享變數,只要對要對共享變數進行修改的程式碼進行同步即可

大家可以點選加入群:【Java高階架構進階群】:854180697 裡面有Java高階大牛直播講解知識點
走的就是高階路線,(如果你想跳槽換工作 但是技術又不夠 或者工作上遇到了瓶頸 ,我這裡有一個JAVA的免費直播課程
,講的是高階的知識點基礎不好的誤入喲,只要你有1-5年的開發經驗可以加群找我要課堂連結 注意:是免費的 沒有開發經驗誤入哦)