1. 程式人生 > >學習筆記(九)併發(二)

學習筆記(九)併發(二)

《Java程式設計思想》整理的一些學習筆記,有不對的地方,歡迎指出。
1.控制執行緒行為的方法——讓步:如果知道run()方法已經完成了所需的工作,可以給執行緒排程機制一個暗示:你的工作已經做的差不多了,可以讓別的執行緒使用CPU了,可以通過呼叫yield()方法來作出(不過這只是個暗示,沒有任何機制保證它將會被採納。)使用yield()以後,程式的輸出會平衡很多,但是如果輸出的字串要再長一點的話,它還會是打破這種平衡,因為呼叫機制是搶佔式的,如果輸出字串過長,佔用時間較長,呼叫機制會在有機會呼叫yield()方法之前切到下一執行緒。


public class Demo extends
Thread{
private int countDown = 5; private static int threadCount = 0; public Demo(){ super(""+ ++threadCount); // 呼叫Tread構造器,給執行緒物件指定一個名字 start(); } public String toString(){ return "#"+getName()+":"+ countDown; // 使用getName()方法獲取執行緒的名字 } public
void run(){ while(true){ System.out.println(this); if(--countDown == 0) return; yield(); } } public static void main(String[] args){ for(int i = 0; i < 5; i++){ new Demo(); } } }

2.另一隻能控制執行緒行為的方法是呼叫sleep(),這將使執行緒停止執行一段時間,該時間由給定的毫秒數決定。如果把上例中的yield()的呼叫換成呼叫sleep():

public class Demo extends Thread{

    private int countDown = 5;
    private static int threadCount = 0;

    public Demo(){
        super(""+ ++threadCount);              // 呼叫Tread構造器,給執行緒物件指定一個名字
        start();
    }

    public String toString(){
        return "#"+getName()+":"+ countDown;  //  使用getName()方法獲取執行緒的名字
    }

    public void run(){
        while(true){
            System.out.println(this);
            if(--countDown == 0)
                return;
            try{
                sleep(100);
            }catch(InterruptedException e){
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{

        for(int i = 0; i < 5; i++){
            new Demo().join();
        }
    }
}

其輸出結果為:

#1:5
#1:4
#1:3
#1:2
#1:1
#2:5
#2:4
#2:3
#2:2
#2:1
#3:5
#3:4
#3:3
#3:2
#3:1
#4:5
#4:4
#4:3
#4:2
#4:1
#5:5
#5:4
#5:3
#5:2
#5:1

在呼叫sleep()方法的時候,必須把它放在try塊中,這是因為sleep()方法在休眠時間到期之前有可能被中斷。如果某人持有對此執行緒的引用,並且在此執行緒上呼叫了interrupt()方法,就會發生這種情況。(如果對執行緒呼叫了wait()或join()方法,interrupt()方法也會對執行緒有影響,所以對這些方法的呼叫也要放在try塊中)通常如果想使用interrupt()方法來中斷一個掛起的執行緒,那麼掛起的時候最好使用wait()而不是sleep(),這樣就不太可能在catch子句裡結束了。
上例中呼叫了join()方法,所以main()在繼續執行之前會等待此執行緒結束在執行下一個,如果不呼叫Join()方法,則輸出還是任意順序,這說明sleep()也不是控制執行緒執行順序的方法,它僅僅使執行緒停止一段時間。(如果必須要控制執行緒的執行順序,最好是根本不用執行緒,而是自己編寫以特定順序彼此控制的協作子程式)。
3.執行緒的“優先權”能告訴排程程式該執行緒的重要性如何。儘管CPU處理現有的執行緒集的順序是不確定的,但是如果有許多執行緒被阻塞並在等待執行,那麼排程程式將傾向於讓優先權最高的執行緒先執行。然而,這並不意味著優先權較低的執行緒將得不到執行(即優先權不會導致死鎖)。優先順序較低的執行緒僅僅是執行的頻率較低。執行緒的優先權是通過使用setPriority()方法進行調整的。

public class Demo extends Thread{

    private int countDown = 5;
//  private static int threadCount = 0;
    private volatile double d = 0;

    public Demo(int priority){
//      super(""+ ++threadCount);              // 呼叫Tread構造器,給執行緒物件指定一個名字
        setPriority(priority);
        start();
    }

    public String toString(){
//      return "#"+getName()+":"+ countDown;  //  使用getName()方法獲取執行緒的名字
        return super.toString()+":"+ countDown; 
    }

    public void run(){
        while(true){
//          System.out.println(this);
//          if(--countDown == 0)
//              return;
//          try{
//              sleep(100);
//          }catch(InterruptedException e){
//              throw new RuntimeException(e);
//          }

            for(int i = 1; i < 100000; i++){
                d = d+(Math.PI+Math.E)/(double)i;
                System.out.println(this);
                if(--countDown == 0)
                    return;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        new Demo(Thread.MAX_PRIORITY);
        for(int i = 0; i < 5; i++){
            new Demo(Thread.MIN_PRIORITY);
        }
    }
}

執行結果:

Thread[Thread-0,10,main]:5
Thread[Thread-0,10,main]:4
Thread[Thread-0,10,main]:3
Thread[Thread-0,10,main]:2
Thread[Thread-0,10,main]:1
Thread[Thread-1,1,main]:4
Thread[Thread-1,1,main]:3
Thread[Thread-3,1,main]:5
Thread[Thread-3,1,main]:4
Thread[Thread-3,1,main]:3
Thread[Thread-3,1,main]:2
Thread[Thread-3,1,main]:1
Thread[Thread-2,1,main]:5
Thread[Thread-2,1,main]:4
Thread[Thread-2,1,main]:3
Thread[Thread-1,1,main]:2
Thread[Thread-4,1,main]:5
Thread[Thread-4,1,main]:4
Thread[Thread-4,1,main]:3
Thread[Thread-4,1,main]:2
Thread[Thread-4,1,main]:1
Thread[Thread-1,1,main]:1
Thread[Thread-2,1,main]:2
Thread[Thread-2,1,main]:1
Thread[Thread-5,1,main]:5
Thread[Thread-5,1,main]:4
Thread[Thread-5,1,main]:3
Thread[Thread-5,1,main]:2
Thread[Thread-5,1,main]:1

在本例中,toString()方法被覆蓋,以便使用Thread.toString()方法來列印執行緒的名稱(可以通過構造器自己設定名稱,這裡是自動生成的名稱)以及執行緒的優先順序、執行緒所屬的“執行緒組”。執行緒0的優先順序最高,其餘執行緒的優先權被設為最低。
當然,對於已存在的執行緒,還可以用getPriority()方法得到其優先權,還可以在任何時候使用setPriority()方法更改其優先權。儘管JDK有10個優先級別,但是與作業系統對映不確定,所以一般只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY三種級別。
4. 後臺程序:是指在程式執行的時候在後臺提供的一種通用服務的執行緒,並且這種執行緒並不屬於程式中不可或缺的部分。因此,當所有的非後臺執行緒結束時,程式也就終止了。即只要有非後臺執行緒還在執行,程式就不會終止。比如,執行main()的就是一個非後臺執行緒。

public class Demo extends Thread{

    public Demo(){
        setDaemon(true);
        start();
    }

    public void run(){
        while(true){
            try{
                sleep(100);
            }catch(InterruptedException e){
                throw new RuntimeException(e);
            }
            System.out.println(this);
        }
    }

    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            new Demo();
        }
    }
}

必須線上程啟動之前呼叫setDaemon()方法,才能把它設定為後臺執行緒。在run()裡面,執行緒被設定為休眠一段時間。一旦所有的執行緒都啟動了,程式馬上會在所有的執行緒能列印資訊之前立刻終止,因為沒有非後臺執行緒(除了main())使得程式保持執行。因此,程式未列印任何資訊就終止了。
可以通過呼叫isDaemon()方法來確定執行緒是否是一個後臺執行緒,如果是一個後臺執行緒,那麼它建立任何的執行緒將被自動設定成後臺執行緒。
5.如果你的類已經繼承了其它的類,就不可能同時繼承Thread,此時可以使用實現Runnable介面的方法來達到上述目的。要實現Runnable介面,只需實現run()方法,而且Thread也是從Runnable介面實現而來的。Runnable類中只有一個run()方法,如果想對你建立的這個Thread物件做點別的事情(比如在toString()裡呼叫getName()),那麼就必須通過呼叫Thread.currentThread( )方法明確得到對此執行緒的引用。例子:

public class Demo implements Runnable{

    private int countDown = 5;

    public String toString(){
        return "#"+Thread.currentThread().getName()+":"+countDown;
    }

    public void run(){
        while(true){
            System.out.println(this);
            if(--countDown == 0)
                return;
        }
    }

    public static void main(String[] args) {
        for(int i = 1; i <= 5; i++){
            new Thread(new Demo(),""+i).start();
        }
    }
}

new Thread(new Demo(),”“+i):呼叫Thread另一個構造器,以Runnable的物件作為引數。
6.當使用了Runnable,通常的意思就是,要用run()方法中所實現的這段程式碼建立一個程序,而不是建立一個物件表示該程序。一個是把執行緒作為一個物件來表示,另一個是作為一個完全不同的一個實體(即程序)來表示。這點是有爭議的。如果僅僅是想開啟一個程序以驅動程式的某個部分,就沒有理由把整個類寫成Runnable型別的。因此,使用內部類把和執行緒有關的程式碼隱藏在類的內部,似乎更合理。有五中內部類的形式:
1)使用普通內部類繼承Thread類
2)使用匿名內部類構造Thread類,重寫run()方法
3)使用普通內部類實現Runnable類
4)使用匿名內部類構造Thread類,引數1構造一個Runnable物件,引數2指明執行緒名稱
5)使用區域性內部類,即在方法內部構造Thread類,重寫run()方法
(推薦使用第五種,程式碼會在後續的文章中給出)
7.建立有響應的使用者介面,下面例子中比較了不使用執行緒和使用執行緒的區別。

class UnresponsiveUI{

    private volatile double d = 1;

    public UnresponsiveUI() throws Exception{
        while(d > 0)
            d = d + (Math.PI+Math.E) / d;
        System.in.read();
    }
}

public class ResponsiveUI extends Thread {

    private static volatile double d = 1;

    public ResponsiveUI(){
        setDaemon(true);
        start();
    }
    public void run(){
        while(true){
            d = d + (Math.PI+Math.E) / d;
        }
    }

    public static void main(String[] args) throws Exception{

        new UnresponsiveUI(); 
        new ResponsiveUI();
        Thread.sleep(300);
        System.in.read();
        System.out.println(d);

    }
}

第一個UnresponsiveUI類中,while迴圈忙於計算,根本達不到System.in.read()這句。第二個ResponsiveUI 類中,由於把執行緒設為後臺執行緒的緣故,一旦main()方法中輸入資料,點選Enter後,main()方法執行結束,後臺執行緒即使沒結束,JVM也已經退出,程式結束。