歡迎您光臨本站 註冊首頁

java thread

←手機掃碼閱讀     火星人 @ 2014-03-09 , reply:0
使用Java多線程編程很容易. Java線程總是實現介面java.lang.Runnable, 一般有兩種方法: 創建一個類實現介面Runnable, 創造該類的實例作為參數傳給Thread構造函數, 創造Thread實例.

package tony.test.testJavaThread;

/**

* @author Tony

*/

public class TestRunnable implements Runnable

{

int count = 0;

private synchronized void printHello()

{

System.out.println( count ". Hello " Thread.currentThread().getName());

}

public void run()

{

printHello();

}

public static void main(String[] args)

{

TestRunnable tr = new TestRunnable();

for (int i=0; i<5; i )

{

new Thread(tr, "Tony's Thread-" (i 1)).start();

}

}

}繼承java.lang.Thread, 創造該類對象. 這種方法一般要重寫(Override)run方法, 不然該線程就什麼也沒做就結束了.

package tony.test.testJavaThread;

/**

* @author Tony

*/

public class TestThread extends Thread

{

static int count = 0;

public TestThread(String name)

{

super(name);

}

public void run()

{

synchronized(this.getClass())

{

System.out.println( count ". Hello " this.getName());

if (count == 2)

{

System.out.println(this.getName() " is to sleep for 4s");

try

{

Thread.sleep(4000);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

}

}

}

public static void main(String[] args)

{

for (int i=0; i<5; i )

{

new TestThread("Tony's Thread-" (i 1)).start();

}

}

}

兩者可以實現同樣的功能. 但個人比較喜歡前者. 由於Java是單繼承, 後者不能再繼承其它類了. 而前者還有個好處就是可以把線程間共享的數據作為類的欄位, 然後把該類實現Singleton, 只實例化一個對象, 作為參數傳給Thread. 當然如果不想共享成員, 而對於每個Thread提供不同的Runnable對象. 而後者要實現共享就要在繼承的類中聲明一堆static屬性.

Java Thread通過方法start啟動, 而實際運行的內容在方法run中. 不過簡單地調用run, 如thread.run(); 只是把run作為普通的方法調用了一遍, 並沒有啟動新的線程. 當run中的內容運行完之後, 線程便自動結束了. 類Thread提供一個靜態方法sleep. 任何時候調用該方法都可以把當前執行的線程暫停指定的時間.



Java Thread同步與synchronized

先說明幾點:

1. 無論synchronized關鍵字加在方法上還是對象上, 它取得的鎖都是對象的鎖 (JDK DOC中描述為an object's monitor), 而不是指對方法或一段代碼加鎖. 加在方法上取得的鎖是該方法所屬對象的鎖, 即加在非static方法上時取得的是this的鎖, 加在static方法上時取得的是class的鎖. 這樣并行調用了一個類的多個對象加了synchronized的方法, 它們之間是沒有絲毫關係的.

2. 每個對象只有一個鎖與之相關聯.而每個線程可以取得N個對象的鎖.

3. 這個鎖實際上是加在對象在堆中的地址上. 像基本類型等位於棧上的是不能加鎖的. 而對象引用本身也是在棧上, 也不會被加鎖. 這就是說像如下的代碼, 實際上兩個線程取得不是同一個鎖:

package tony.test.testJavaThread;

/**

* @author p465890

*/

public class TestSynchronized

{

public static void main(String[] args)

{

Runnable rb = new Runnable()

{

// It is said that new Byte[0] is more effective than new Object()

// For the former needs two byte codes and the later needs three.

// I have not digged into it.

// Maybe the reason is as follows.

// "Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader."

byte[] bt = new byte[0];

public void run()

{

synchronized(bt)

{

for (int i=0; i<5; i )

{

try

{

Thread.sleep(50);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

bt = new byte[0];

System.out.println(Thread.currentThread().getName());

}

}

}

};

for (int i=0; i<5; i )

{

try

{

Thread.sleep(100);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

new Thread(rb).start();

}

}

}

4. 實現同步是要很大的系統開銷作為代價的, 不需要同步就不要做同步.



Wait, notify, notifyAll

這三個函數都是Object的成員方法, 也就是說所有的對象都能調用這三個函數, 這與所有的對象上都有鎖是一致的, 這三個方法的也是對自己所屬對象鎖進行操作, 來影響需要該對象鎖的線程的行為.

注意下面所說的new, run, schedule, wait, sleep, dead狀態並不是官方的說法, 但這樣解釋可以理解很多問題. 並且目前還沒碰到解釋不通的現象.

這樣說吧, 任何一個對象的鎖就好比通行證, synchronized就用來標誌某一塊程序只有用它指定的對象的通行證才能進入. 這種通行證每個對象都有且只有一個, 而一個synchronized也能且只能指定一個對象(當然synchronized可以嵌套, 但每一個synchronized也只能指定一個對象), 多個synchronized可以指定同一個對象, 對象的通行證只有一個, 這幾個synchronized塊中的東西就如同一塊一樣, 不能同時進入. 但是沒有用synchronized修飾的程序自然不需要通行證, 無論哪個線程都可進入.

一個線程創建好了, 但沒啟動 (沒調用start), 此時它處於new狀態, 一個線程結束了就處於dead狀態. 這兩種狀態很簡單, 不再多說.

對於線程來說, 要麼進入不需通行證的代碼(沒用synchronized修飾的代碼), 要麼想方設法取得通行證. 但線程不像人, 它是很守規矩的. 當一個線程想進入synchronized塊時, 如果該synchronized指定的對象的通行證沒被其他線程使用, 它就拿走通行證進入該synchronized塊, 注意, 一量它出來, 它一定會把通行放回原處. 此時它處於run狀態. 但此時又有線程來了, 它也想進入該synchronized塊, 它只能等待在run狀態的線程歸還通行證, 它會一直這樣耐心等待. 如果此時又有其他線程來了, 這樣就有了一個等待的隊列, 我們稱這些線程處於schedule狀態, 這種狀態的線程一切都準備好了, 只等著有通行證就進入run狀態. 當然當通行證可用時, 只有一個線程能夠得到, 其他仍然處於schedule狀態. 不過到底誰有幸得到通行證, 那就由具體的JVM決定了, 程序員千萬別想著JVM會跟自己想法一樣. 我說過, 線程很守規矩. 實際上線程們還很友好. 一個處於run狀態的線程run了一會之後, 它可能想讓其他線程先run一把, 於是它就調用它擁有的通行證的對象的wait方法, 告知自己要wait了, 此時通行證歸還, 而它自己則進入wait狀態. 這個線程可以設定它將處於wait狀態的時間或者無限長(參數0時無限長, 負數會拋java.lang.IllegalArgumentException異常. 一旦時間到了, 就自動進入schedule狀態, 等待通行證. 在這個時間之內, 除非其他線程喚醒它, 否則它將一直不聞不問了, 一直wait, 實際上說sleep更確切. 怎麼喚醒在下面會講.既然線程如此友好, 自然JVM也待它不薄, 一個線程wait后, 當它重新有機會進入schedule, 再進入run, 它裝從wait的下一個語句開始執行, 就好像它沒有wait過一樣. 當很多線程都wait之後, 就有了wait隊列, 而處於run的線程, 可能會想喚醒一個或一些wait的進程, 它可以調用擁有的通行證的對象的notify方法(喚醒一個線程, 具體哪一個, 也是JVM說了算)或者notifyAll方法(全部喚醒). 這裡需要說明的是, 被喚醒的是那些處於相應對象通行證wait隊列的線程, 其它無關的線程並不會被喚醒, 喚醒之後處於schedule狀態, 此時通行證仍然為那個run狀態的線程所有, 到底誰能進入run狀態, 也是JVM決定的. 有時線程太好心了, 沒有某個對象的通行證, 卻硬要調用這個對象的wait, notify或notifyAll方法, 結果會拋出java.lang.IllegalMonitorStateException異常.



注意上面所講的, 各個通行證之間是獨立的, 比如, 一個線程調用一個對象的wait, 它歸還了這個對象的通行證, 但要是它還擁有其它對象的通行證, 它會仍然擁有, 當然這可能造成死鎖. 不過這應該是程序員的責任.

類Thread

難道所有線程都這麼公平, 當然不是. 類Thread提供了很多方法都針對某個線程進行操作, 它要麼不影響要麼影響指定線程所擁有的鎖. 也就是說這些方法與上面的不同, 是針對線程而不是針對鎖的.

一個線程處於wait時, 除了上面講的等到指定時間, notify, notifyAll之外, 它還能被其他線程interrupt喚醒. interrupt()是Thread類的方法. 它作的是指名道姓的喚醒, 一個線程想喚醒另一個線程, 它就必須直接叫那個線程的名字, 即調用那個線程的interrupt()方法. 這個處於wait的線程就會拋出java.lang.InterruptedException異常, consume異常之後, 該線程就會進入到schedule狀態, 並且如果之前其interrupt status(這個status與前面所提的run, schedule, wait狀態沒關係, 其實更該稱之為屬性, 估計這就是Thread類的屬性, 只是JDK DOC 上這樣稱呼. 這裡用status以示區別), 這個interrupt status會被清除. 這裡要說明一下, 線程還有sleep狀態, 這種狀態行為與wait基本一樣, 只是不能被nofity和notifyAll喚醒. 線程可以調用Thread的靜態方法sleep, 使當前線程(自己)進入sleep狀態, 參數指定時間, 或Thread的成員方法join, 參數幾乎和調用wait一樣, 但它是使自己處於進入sleep狀態, 一直到被調用線程運行完了, 或者時間到了, 它才繼續運行.

package tony.test.testJavaThread;

/**

* @author Tony

*/

public class TestJoin implements Runnable

{

int count = 0;

public static void main(String[] args)

{

TestJoin tj = new TestJoin();

for (int i=0; i<5; i )

{

new Thread(tj, "hello" i).start();

}

}

public void run()

{

try

{

Thread.sleep(1000);

}

catch (InterruptedException e1)

{

e1.printStackTrace();

}

if (count == 0)

{

count ;

Thread thread1 = new Thread(this, "helloSon");

thread1.start();

try

{

thread1.join(3000);

}

catch (InterruptedException e)

{



e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName());

}

}

只是Thread的這幾個方法顯然與擁有哪個對象的通行證沒有關係. 實際上他們擁有的對象的通行證沒有任何改變, 否則是不安全的. 實際上像stop, suspend, resume等等deplicated的方法要麼調用時會釋放所有它擁有的鎖(通行證), 導致不安全, 要麼調用之後一定要某個方法來重新啟動, 容易造成死鎖. sleep狀態和wait一樣的是都能被interrupt喚醒, 喚醒的行為一模一樣. 如果一個線程沒有處於wait和sleep狀態, 並且也沒有」 blocked in an I/O operation upon an interruptible channel」或」blocked in a Selector」, 它的interrupt status會被設置. Thread同時提供了兩個方法來查詢一個進程的interrupt status: 一種是靜態方法interrupted()查詢當前進程的interrupt status, 返回boolean值, 同時清除interrupt status; 另一種是成員方法isInterrupted(), 但它不會清除interrupt status. 要想查詢當前線程interrupt status又不想清除之, 可以這樣: Thread.currentThread.isInterrupted().

前面所說的一開始線程的interrupt status都為false, 如果一開始interrupt status就被設為true, 那麼該線程一調用wait, sleep或join就會拋出java.lang.InterruptedException異常, 並且interrupt status被清除. 實際上我們如果把interrupt()的調用看到先設置interrupt status, 接下來的行為就一致了, 而不管是先設interrupt status, 還是先進為wait或sleep狀態.

package tony.test.testJavaThread;

/**

* @author Tony

*/

public class TestInterrupt implements Runnable

{

int count = 0;

private synchronized void printHello()

{

System.out.println( count ". Hello " Thread.currentThread().getName());

System.out.println(Thread.currentThread().getName() ": " Thread.currentThread().isInterrupted());

if (count == 1)

{

try

{

wait();

}

catch (InterruptedException e)

{

System.out.println(Thread.currentThread().getName() ": " Thread.currentThread().isInterrupted());

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() " is back");

}

System.out.println(Thread.currentThread().getName() ": " Thread.currentThread().isInterrupted());

}

public void run()



{

printHello();

}

public static void main(String[] args)

{

TestInterrupt tr = new TestInterrupt();

Thread[] threads = new Thread[5];

for (int i=0; i<5; i )

{

threads[i] = new Thread(tr, "Tony's Thread-" (i 1));

threads[i].start();

threads[i].interrupt();

}

try

{

Thread.sleep(2000);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() ": " Thread.interrupted());

threads[0].interrupt();

}

}

線程們之間友好還體現在Thread靜態方法yield(), 它所做的是讓自己暫停一下. 實際上就相當於把自己的狀態從run變到schedule, 重新競爭通行證(鎖), 到底誰會進入run, 或者它自己又進入了run, 由JVM決定.

線程可以設置或查詢優先順序, 高優先順序進程優先. 線程還能設置或查詢是否成為Daemon線程. Daemon線程和非Daemon線程的區別是JVM對它們的態度不同. 只要沒有非Daemon線程存在, JVM就退出, 就像調用了Sysem.exit()一樣, 而不管到底有沒有或有多少Daemon線程仍在運行.

類ThreadGroup

至於ThreadGroup, 它所做的就是把線程分組來管理, 比如, 調用ThreadGroup的interrupt方法, 相當於調用組內所有線程的interrupt方法. 組是樹裝結構的, 樹的根結點組的名稱是」system」. 如果不指定組名, 創建的Thread或ThreadGroup就是當前組的子結點. Java程序main方法所在的組名為」main」, 是」system」的子結點, 調用ThreadGroup的list方法就可以把從該組開始的樹狀結構比文本形式列印出來.

ThreadGroup和Thread的name都可以一樣.

package tony.test.testJavaThread;

/**

* @author p465890

*/

public class TestThreadGroup

{

public static void main(String[] args)

{

ThreadGroup tg = new ThreadGroup("tg-hello");

new Thread(tg, new Runnable()

{

public void run()

{

}

}, "th-world");

ThreadGroup tg2 = new ThreadGroup("tg-hello");

tg.getParent().getParent().list();

}

}

ThreadGroup提供了一個處理非Catch異常的方法, 只要重載ThreadGroup中的uncaughtException方法, 屬於該組的Thread只要拋了非Catch異常, 就會調用此方法.



package tony.test.testJavaThread;

/**

* @Tony

*/

public class TestUncaughtException extends ThreadGroup

{

public TestUncaughtException(String name)

{

super(name);

}

public void uncaughtException(Thread t, Throwable e)

{

System.out.println(e.getClass().getName() " occurs in thread " t.getName());

e.printStackTrace();

}

public static void main(String[] args)

{

new Thread(new TestUncaughtException("testUncaughtException"), new Runnable()

{

public void run()

{

System.out.println("Test uncaughtException: 1/0");

int i = 1 / 0;

System.out.println("Test uncaughtException ends");

}

}).start();

}

}

對於JDK1.5以止版本, Thread本身就提供了setUncaughtExceptionHandler, 在線程粒度處理非Catch異常. JDK DOC中描述如下:

「Uncaught exception handling is controlled first by the thread, then by the thread's ThreadGroup object and finally by the default uncaught exception handler. If the thread does not have an explicit uncaught exception handler set, and the thread's thread group (including parent thread groups) does not specialize its uncaughtException method, then the default handler's uncaughtException method will be invoked. 「

package tony.testJavaThread;

public class TestUncatchedExceptionHandler

{

public static void main(String[] args)

{

Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()

{

public void uncaughtException(Thread t, Throwable e)

{

System.out.println(e.getClass().getName() " occurs in thread " t.getName());

e.printStackTrace();

}

});

int i = 1 / 0;

}

}Thread還提供方法getContextClassLoader, 得到該線程的ClassLoader對象.


[火星人 ] java thread已經有781次圍觀

http://coctec.com/docs/java/show-post-59886.html