Java基礎加強之多線程篇 - 線程創建與終止、互斥、通信、本地變量

swiftchen 8年前發布 | 10K 次閱讀 多線程 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(); }         }         ...... } </code></pre>

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

} </code></pre>

可以寫成內部類的形式, 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());
}

} </code></pre>

運行結果

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();
    }
}

} </code></pre>

運行結果

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);
            }
        }
    }
}

} </code></pre>

運行結果

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();
}

} </code></pre>

運行結果

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;  </code></pre>

    下面我們重點分析 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); }   </code></pre>

    先獲取當前的線程,然后通過 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;
      

      } }  </code></pre>

      接下來我們看看 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); }  </code></pre>

    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(); }   </code></pre>

    先獲取當前的線程,然后通過 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; }   </code></pre>

    總的來講,每創建一個線程( 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</code></pre>

運行結果


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</code></pre>

創建 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  </code></pre>

運行結果


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</code></pre>

線程共享變量,只要對要對共享變量進行修改的代碼進行同步即可。

 

來自:http://www.cnblogs.com/hjwublog/p/6133278.html

 

 本文由用戶 swiftchen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!