本文由@呆代待殆原創,轉載請注明出處:http://www.cnblogs.com/coffeeSS/
Java中實現多線程的方法
實現Runnable接口
實現Runnable接口裡的run()方法,並將這個實例提交給一個Thread構造器,最後調用Thread.start()就可以啟動一個線程。
1 public class MyRunnable implements Runnable { 2 String name; 3 public MyRunnable(String name){ 4 this.name=name; 5 } 6 @Override 7 public void run() { 8 for(int i=0;i<5;++i){ 9 System.out.println(name+" 第 "+i+" 次運行"); 10 try { 11 Thread.sleep(1000); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 System.out.println(name+" 運行完畢停機"); 17 } 18 19 } View Code 1 public class Test { 2 public static void main(String[] args){ 3 new Thread(new MyRunnable("0號機")).start(); 4 new Thread(new MyRunnable("1號機")).start(); 5 } 6 } View Code
繼承Thread
直接繼承Thread類,重寫run()方法,調用Thread.start()即可
1 public class MyThread extends Thread { 2 public static void main(String[] args) { 3 MyThread zero=new MyThread("0號機"); 4 MyThread one =new MyThread("1號機"); 5 zero.start(); 6 one.start(); 7 } 8 String name; 9 public MyThread(String name){ 10 this.name=name; 11 } 12 public void run(){ 13 for(int i=0;i<5;++i){ 14 System.out.println(name+" 第 "+i+" 次運行"); 15 try { 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 System.out.println(name+" 運行完畢停機"); 22 } 23 } View Code
線程的狀態
線程有五個狀態。
1、初始狀態
Thread aThread = new Thread();
2、就緒狀態
Thread aThread = new Thread();
Thread.start();
3、阻塞狀態
suspend()
sleep()
wait()
輸入輸出流發生阻塞。
線程同步時試圖鎖住另一個線程鎖住的對象。
...
4、運行狀態
run()方法正在執行中。
5、死亡狀態
stop(),或非預期的異常終止run()方法,線程突然死亡。
run()正常退出,線程自然死亡。
線程相關的常用方法簡介(都有相應的在線API鏈接)
start()
//啟動線程,調用run()方法。
run()
//一般我們需要重寫這個方法給線程安排要執行的任務。
interrupt()
//中斷這個線程。
yield()
//會讓調用這個方法的線程睡眠。
wait()
//讓調用此方法的線程阻塞。
wait與sleep是不同的,調用了sleep 的線程仍然會占用cpu,且不會釋放已經拿到的鎖,但是不會有任何動作,調用了wait的線程不會占用cpu,會讓出已經占用的鎖。
sleep(long millis)
//讓當前線程睡眠millis毫秒
sleep(long millis, int nanos)
//讓當前線程睡眠millis毫秒nanos納秒。
notify()
//隨機喚醒一個阻塞狀態的線程。
notifyAll()
//喚醒所有阻塞狀態的線程。
setPriority(int newPriority)
//用來設置線程的優先級JDK提供了10個優先級所以newPriority的取值是1-10,其中10是最高優先級。
注意大多數操作系統並不能和這10個優先級很好的映射,比如window只有7個優先級,而Sun的Solaris有231個優先級,所以要想使優先級的設置操作是可移植的,最好用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這三個預定義的常數來設置優先級。
getPriority()
//返回線程優先級。
toString()
//返回線程的名字、優先級和線程組。
setName(String name)
//設置線程的名稱。
getName()
//獲得線程的名字。
getId()
//獲得線程的identifier
setDaemon(boolean on)
//標識是否是一個守護線程。
join()
//當前線程會被掛起,等待調用這個方法的目標線程執行結束後再被喚醒(如:你在某個線程裡寫t.join(),則當前線程會掛起,t線程開始運行,t線程返回後,當前線程繼續執行)。
join(long millis)
//作用同上,但是如果millis毫秒後目標線程還沒返回的話,目標線程會被強制中斷,然後返回當前線程。
join(long millis, int nanos)
//同上,只是時間更精確了。
isAlive()
//測試這個線程是否還活著。
interrupted()
//測試這個線程是否被中斷。
關於線程的同步
synchronized關鍵字
synchronized有兩種用法,用來修飾方法和用來修飾代碼塊。
1,修飾方法
synchronized關鍵字修飾方法的時候,這個方法只能被一個線程調用,當第二個線程想同時訪問的時候,它將被阻塞直到第一個線程從方法返回。
如果一個對象中有很多個方法都被synchronized關鍵字修飾,由於它們是共享同一把鎖的,也就是說,對其中某一個方法進行調用,將使得所有synchronized方法都不能被這個線程以為的其他線程調用。
這裡要注意類的每一個實例都有一個自己的對象鎖,一個實例裡面的synchronized方法被訪問會導致這個實例內其他synchronized方法不能訪問,但是不會導致這個類的其他實例的synchronized方法不能用。
2,修飾代碼塊
修飾代碼塊的時候synchronized可以指定需要獲取的鎖,而且可以讓同步的代碼塊更加精確。
關於鎖,每個類有一個類鎖,每個類的實例有一個對象鎖,這兩把鎖互相不干擾。
當synchronized修飾static方法或者修飾的代碼塊指定的鎖為XXX.class的話,將獲得類鎖,類鎖管理所有的static方法的訪問,獲取類鎖後,其他類的實例將無法訪問所有的static方法,但是非static方法即使是用synchronized修飾的也可以訪問。同理,即使獲得對象鎖後將使得其他非static的synchronized修飾的方法無法訪問,但是static方法可以被訪問,當然同一個對象可以同時獲得者兩把鎖。
另外類鎖只是一個抽象概念,並不存在真正的類鎖,這裡的類鎖是代表著這個類的對象的對象鎖。
使用Lock對象進行並發控制。
使用Lock對象的時候要注意有良好的格式,如下(圖片來自網絡)
lock.lock()方法將會讓線程獲得鎖,如果這個時候有別的線程來取鎖,則會陷入阻塞狀態。
lock.unlock()方法會釋放鎖,寫在finally裡的原因是為了讓任何情況下,鎖都能得到釋放,這裡要強調的是return語句要寫在try裡面,以確保unlock()不會過早的發生。
使用Lock與使用synchronized的區別在於,如果使用synchronized,如果線程沒有得到鎖,它將一直等待下去,如果使用Lock,通過調用tryLock方法可以實現在失敗的時候中斷等待去干別的事情。詳細情況如下。
tryLock()
//如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false。
tryLock(long timeout, TimeUnit unit)
//如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false。
效率上,在資源競爭不是很激烈的情況下,synchronized的性能要更好,否則synchronized的性能會下降的很快,而Lock的性能一直很穩定。
死鎖簡介
當以下4個條件同時滿足的時候就會發生死鎖
1,互斥條件:存在不能共享的資源。
2,不可搶占條件:資源申請者不能強行的從資源占有者手中奪取資源,資源只能由占有者自願釋放。
3,請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
4,環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。
對於java線程的鎖機制來說,互斥條件和不可搶占條件先天滿足,所以我們能做到的就是不要讓條件3和條件4也都滿足就可以了。具體細節這裡不做討論。
管道流用於線程間通信
管道流的應用非常簡單,相關的類有四個
用於字節流的PipedInputStream與PipedOutputStream
用於字符流的PipedReader與PipedWriter
它們都是配套使用的,用於兩個線程間的通信,並且通信是單向的。
使用的基本流程如下,
1,創建對應的輸出流和輸入流。
2,用connect方法將它們連接起來,然後就可以使用了。
3,調用close方法釋放資源。
輸入流的read方法在沒有更多數據的時候會自動阻塞。
另外用管道流進行讀寫的時候必須保證相對應的兩個線程都不能退出,也不能阻塞。否則就會報java.io.IOException: Write/Read end dead 的錯誤。
下面給出一個實例,用PipedReader與PipedWriter實現兩個線程間的通信,程序會不停的打印一段文字。
1 public class TestPiped { 2 PipedWriter pWriter; 3 PipedReader pReader; 4 5 public TestPiped() { 6 pWriter = new PipedWriter(); 7 try { 8 pReader = new PipedReader(pWriter); 9 } catch (IOException e) { 10 e.printStackTrace(); 11 } 12 new Thread(new MyReader(pReader)).start(); 13 new Thread(new MyWriter(pWriter)).start(); 14 } 15 16 public static void main(String[] args) { 17 new TestPiped(); 18 } 19 } 20 21 class MyReader implements Runnable { 22 PipedReader pReader; 23 24 public MyReader(PipedReader pReader) { 25 this.pReader = pReader; 26 } 27 28 @Override 29 public void run() { 30 while (true) { 31 char c; 32 int i; 33 while (true) { 34 try { 35 i = pReader.read(); 36 if (i == -1) 37 break; 38 c = (char) i; 39 System.out.print(c); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } 43 } 44 System.out.println(); 45 notifyAll(); 46 } 47 } 48 } 49 50 class MyWriter implements Runnable { 51 PipedWriter pWriter; 52 53 public MyWriter(PipedWriter pWriter) { 54 this.pWriter = pWriter; 55 } 56 57 @Override 58 public void run() { 59 while (true) { 60 try { 61 pWriter.write(new String("番茄,番茄,這裡是西紅柿,收到請回答\n")); 62 Thread.sleep(2000); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 } View Code
參考資料:
1,《java編程思想》中文 第四版
2,在線java API http://tool.oschina.net/apidocs/apidoc?api=jdk_7u4