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()方法。
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();
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類提供的一些方法來終止線程。
stop()方法沒有做任何的清除操作就粗暴終止線程,釋放該線程所持有的對象鎖(下文將介紹),受該對象鎖保護的其它對象對其他線程可見,因此具有不安全性。
suspend()方法會使目標線程會停下來,但仍然持有在這之前獲得的對象鎖,對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。
終上所述,不建議使用stop()方法和suspend()方法來終止線程,通常我們通過interrupt()方法來終止處於阻塞狀態和運行狀態的線程。
需要注意的是,interrupt()方法不會中斷一個正在運行的線程,僅僅是將線程的中斷標記設為true,當調用了阻塞方法之後,線程會不斷監聽中斷標志,如果為true,則產生一個InterruptedException異常,將InterruptedException放在catch中就能終止線程。
isInterrupted()方法可以返回中斷標記,常用循環判斷條件。
interrupted()方法測試當前線程是否已經中斷,線程的中斷標志由該方法清除。interrupted()除了返回中斷標記之外,它還會清除中斷標記。
看下面例子
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類的源碼中,可以看到線程有以下幾種狀態: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同時調用output方法進行輸出,從運行結果可以看出,線程之間沒有執行完各自的輸出任務就被交替了運行了。下面通過對象的鎖實現線程1和線程2對output方法的互斥訪問。
使用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 對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的一些常用用法。
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 和對象鎖能實現線程互斥,但是不能實現線程通信。
線程之間的通信通過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()的作用是給線程調度器一個提示,告知線程調度器當前線程願意讓出CPU,但是線程調度器可以忽略這個提示。因此,yieId()的作用僅僅是告知線程調度器當前線程願意讓出CPU給其他線程執行(竟然只是願意,當前線程可以隨時反悔,那其他線程也不一定能得到CPU執行),而且不會讓當前線程釋放對象鎖。
yieId()能讓當前線程由運行狀態進入到就緒狀態,從而讓其它具有相同優先級的等待線程獲取執行權。但是,並不能保證在當前線程調用yield()之後,其它具有相同優先級的線程就一定能獲得執行權,也有可能當前線程又進入到運行狀態繼續運行。
yieId()只建議在測試環境中使用。
wait()和yield()的區別
(1)wait()是讓線程由運行狀態進入到等待(阻塞)狀態,而yield()是讓線程由運行狀態進入到就緒狀態。
(2)wait()是讓線程釋放它所持有對象的鎖,而yield()方法不會釋放鎖。
下面的例子是“主線程輸出三次接著子線程輸出三次”,重復兩次。
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
在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。看下面的例子。
運行結果
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
線程共享變量,只要對要對共享變量進行修改的代碼進行同步即可。