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()方法html

繼承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();java

實現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()方法會使目標線程會停下來,但仍然持有在這以前得到的對象鎖,對任何線程來講,若是它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會形成死鎖。app

 

終上所述,不建議使用stop()方法和suspend()方法來終止線程,一般咱們經過interrupt()方法來終止處於阻塞狀態和運行狀態的線程dom

 

須要注意的是,interrupt()方法不會中斷一個正在運行的線程,僅僅是將線程的中斷標記設爲true,當調用了阻塞方法以後,線程會不斷監聽中斷標誌,若是爲true,則產生一個InterruptedException異常,將InterruptedException放在catch中就能終止線程。eclipse

 

isInterrupted()方法能夠返回中斷標記,經常使用循環判斷條件。ide

 

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

 

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,且發出異常後線程被喚醒,以便線程能從異常中正常退出。

 

線程運行狀態圖

線程從建立到終止可能會經歷各類狀態。在java.lang.Thread.State類的源碼中,能夠看到線程有如下幾種狀態:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。各類狀態的轉換以下:

 

wps98A1.tmp 

 

當經過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同時調用output方法進行輸出,從運行結果能夠看出,線程之間沒有執行完各自的輸出任務就被交替了運行了。下面經過對象的鎖實現線程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和線程2output方法實現了互斥訪問。

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)方法獲取到一個mapmap的類型爲ThreadLocalMap

這個map其實就是存儲線程變量的對象threadLocalsThreadLocalMap是ThreadLocal中的一個內部類,是一個定製的hashmap以便適用於存儲線程本地變量。居然是定製的hashmap,那麼就有Entry tablehashmap的內部實現參考上一篇: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方法初始化。setInitialValueset方法惟一不一樣的是調用了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

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

相關文章
相關標籤/搜索