2015年9月3日 星期四

Java 執行緒操作 (Thread)

日常寫程式中,可能會因為單一的運算實在是太慢,又或者做網路連線時,必須採用多人連線,需要分工處理,因此就得需要用 "多工",在java裡面使用多執行緒達成!


建立有執行緒的方法


一般執行緒實作建立可用兩種:
1. 透過 implements 實作 Runnable 介面
class Pi implements Runnable{ ...

2. 透過 extends 繼承 Thread 類別
class Pi extends Thread{ ...

完成後,兩者皆要實作 run 這個方法(Override)
@Override public void run(){ ...

所以說,"run" 這個方法將會成為每次要建立這個物件的執行緒時,首要進入的方法。

* run 不可以拋出例外(throws),而且不建議重寫原本的方法,所以裡面發生例外就必須寫 try catch。

* 以上兩者實作的方法用途皆相同,但寫extends可能會因為您的物件有其他父類別要繼承,故常用 implements 來時做 Runnable 介面達成!

實作執行緒方法

1. 使用第一種方式 (implements)來實作執行緒:
public class Main{
    public static void main(String[] args) {
        Thread t1 = new Thread(new Pi());
        t1.start();
        
        Thread t2 = new Thread(new Pi());
        t2.start();
    }
}
class Pi implements Runnable{
 @Override
 public void run(){
  System.out.println("Thread ID:"+Thread.currentThread().getName()+" print 3.1415926");
 } 
}

2. 使用第二種方式 (extends)來實作執行緒:
public class Main{
    public static void main(String[] args) {
        Thread t1 = new Thread(new Pi());
        t1.start();
        //also can use:
        Pi pi = new Pi();
        pi.start();
    }
}
class Pi extends Thread{
 @Override
 public void run(){
  System.out.println("Thread ID:"+Thread.currentThread().getName()+" print 3.1415926");
 } 
}

兩者皆可得出結果:
Thread ID:Thread-0 print 3.1415926
Thread ID:Thread-1 print 3.1415926

若要設定執行緒 ID, 可以透過:
...
t1.setName("第一執行緒");
pi.setName("第二執行緒");
...

結果可得:
Thread ID:第一執行緒 print 3.1415926
Thread ID:第二執行緒 print 3.1415926

停止等待方法

若希望程式暫時停止幾秒鐘,可以透過以下方法:
Thread.sleep(2000); //stop 2000 million seconds
這是一個靜態的方法,直接呼叫即可。

執行緒讓出 (yield)

*以下全部使用 implements 實作的方式來作範例
若不希望目前執行緒繼續執行,可以於 run()  裡面,使用 yield() 來讓出,但是一次只能讓出一次,如果要實現多次讓出,請用迴圈(while)。
public void run(){
    for (int i = 0; i <2147483647; i++) {
        Thread.yield();
    }
    System.out.println("Thread ID:"+Thread.currentThread().getName()+" print 3.1415926");
}

*yield 也是一個靜態的方法,直接呼叫 Thread.yield() 即可,若您是使用 extends 來時做,您也可以用 yield() 。

凍結等待其他執行緒方法 (join)

* 為了說明其功用,以下是範例 code:
class Pi implements Runnable{
 @Override
 public void run(){
   Thread processor = new Thread(new MathMachine());
   System.out.println("交給機器運算...");
   processor.start();
   System.out.println("OK, Thread ID:"+Thread.currentThread().getName()+" print 3.1415926");   
 } 
}

class MathMachine implements Runnable{
    @Override
    public void run(){
        System.out.println("PI 運算完成");
    } 
}

先列出結果:

交給機器運算...
OK, Thread ID:第一執行緒 print 3.1415926
PI 運算完成

其實可以發現上方的結果已經有問題了,程序應該是等待 MathMachine 運算完成才能執行顯示動作,這時候就可以使用 join 這個方法,優先讓 MathMachine 執行:

...
processor.start();
try {
    processor.join();
} catch (InterruptedException ex) {

}
System.out.println("OK, Thread ID:"+Thread.currentThread().getName()+" print 3.1415926");
...
使用 processor.join(), 就可以優先讓數學機器(MathMachine)執行完,再執行下列方法,其實就是將 Pi 這個類別的執行緒進入凍結狀態,等待 MathMachine 執行緒完成後,才解除 Pi 的凍結狀態回到執行緒中。

*使用 join 將會拋出一個 Interrupted 的例外,在此不做處理

正確結果為:

交給機器運算...
PI 運算完成
OK, Thread ID:第一執行緒 print 3.1415926

另外有幾種運算子,不多做介紹:
wait() <-- 針對呼叫這個方法的原方法,把執行緒做暫停等待
notify() <-- 隨意把目前暫停的方法呼叫繼續執行

若程式中沒有良好的機制喚醒使用 wait() 的方法,那麼程式可能就會互相一直等待(dead lock)。

synchronized 運算子,可以使用範圍的synchronized:
synchronized(this){
}
這個做法是範圍技,可以鎖定需要的物件做同步化 (this這行鎖定物件)。
或是在方法宣告中,加上 synchronized:
public synchronized void HelloWorld(){ ...

所有使用這個方法的傢伙,不管是多執行緒,一次就只能一個處理 / 存取這個項目,舉個例子就是 ATM, 總不能兩個人同時存錢又取錢,結果資料被覆蓋了吧?

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014