JAVA並行程式基礎

來源:互聯網
上載者:User

標籤:死迴圈   tac   決定   優先順序   obj   設定   script   隊列   while   

JAVA並行程式基礎一、有關線程你必須知道的事進程與線程
  1. 在等待面向線程設計的電腦結構中,進程是線程的容器。我們都知道,程式是對於指令、資料及其組織形式的描述,而進程是程式的實體。
  2. 線程是輕量級的進程,是程式執行的最小單位。(PS:使用多線程去進行並發程式的設計,是因為線程間的調度和切換成本遠小於進程)
  3. 線程的狀態(Thread的State類):

    • NEW–剛剛建立的線程,需要調用start()方法來執行線程;
    • RUNNABLE–線程處於執行狀態;
    • BLOCKED–線程遇到synchronized同步塊,會暫停執行直到獲得請求的鎖;
    • WAITING–無限制時間的等待;
    • TIMED_WAITING–有限制時間的等待;
    • TERMINATED–執行完畢,表示結束。
二、線程的基本操作建立線程

程式碼範例:start()方法會建立線程並調用run()方法!

Thread t = new Thread();t.start();

注意以下代碼:這段代碼也可以編譯執行,但不能建立一個線程,而是在當前線程中調用run()方法。(PS:就是作為一個普通方法來調用)

Thread t = new Thread();t.run();

不能用run()來開啟線程,它只會在當前線程中串列執行run()方法。

另外,Thread類有一個非常重要的構造方法:
public Thread(Runnable target)
這個構造方法在start()調用時,新線程會執行Runnable.run()方法。實際上,預設的Thread.run()也是這樣子做的。

public class MyThread implements Runnable {    public static void main(String[] args) {        Thread t = new Thread(new MyThread());        t.start();    }    @Override    public void run() {        System.out.println("開啟新線程執行run()方法");    }}
  • 繼承Thread的方式定義線程後,就不能在繼承其他的類了,導致程式的可擴充性大大降低。而且**重載**run()方法也只是和普通方法一樣,不會被JVM主動調用。
  • 使用實現Runnable介面並傳入執行個體給Thread,可以避免重載Thread.run(),單純使用介面來定義Thread。
終止線程

JAVA提供了一個stop()方法來關閉線程,不過這是一個被標註為廢棄的方法。原因是stop()方法太過暴力,強行將執行到一半的線程關閉可能導致一些資料不一致的問題。

一幫情況下,不要使用 stop() 方法!

解決方案:定義一個標記變數stopme,用於指示線程是否需要退出。

線程中斷

在JAVA中,為了完善安全執行緒退出,有一種重要的線程協作機制————線程中斷。(注意:線程中斷會給線程發送一個通知,但線程接到通知後如何處理,則由目標線程自行決定)

  • 線程中斷的三個重要方法
    public void Thread.interrupt()                  //中斷線程    public boolean Thread.isInterrupted()           //判斷線程是否被中斷    public static boolean Thread.interrupted()      //判斷線程是否被中斷,並清除當前中斷狀態
  • Thread.interrupt()方法中斷,線程不會立刻停下來,比如死迴圈體。可以通過isInterrupted()方法來判斷是否跳出迴圈體。
線程休眠
  1. Thread.sleep()
    讓當前線程休眠若干時間,其簽名如下:
    public static native void sleep(long millis) throws InterruptedException

    • InterruptedException不是運行時異常,當線程處於休眠狀態時,如果被中斷就會拋出這個異常。
    • sleep()方法由於中斷拋出異常時,會清除中斷標記。所以,在異常處理中需要再次設定中斷標記!
  2. Thread.wait()與notify()
    這兩個方法是配套的,一個線程調用obj.wait()處於等待狀態,需要其他線程調用obj.notify()來喚醒。obj對象就是多個線程間有效通訊手段。

    • Object.wait() 必須包含在 s’y’nchronzied 語句中,無論是 wait() 還是 notify() 都需要先獲得目標對象的一個監視器。(PS:這兩個方法都是執行後立刻釋放監視器,防止其他等待對象線程因為該線程休眠而全部無法正常執行)
    • Object.wait() 同樣會拋出InterruptedException異常。
    • Object.notifyAll() 方法會喚醒等待隊列中所有等待的線程。
  3. suspengd()與resume()
    前者是線程掛起,後者是繼續執行,這是一對互相配合的方法。這兩個也是已經被廢棄的方法,瞭解一下就行了。

    • suspend() 會導致休眠線程的所有資源都不會被釋放,直到執行 resume() 方法。(如果兩個方法,後者執行於前者之前,則線程很難有機會被繼續執行)
    • 對於被掛起的線程,其狀態是Runnable,這會嚴重影響到我們對於系統目前狀態的判斷。(不可饒恕)
  4. join()與yield()

    • join() 方法
      其簽名如下:
      public final void join() throws InterruptedException;
      public final void join(long millis) throws InterruptedException;
      第一個方法表示無限等待;第二個方法表示一定時間的等待。join()方法會阻塞當前線程,直到目標線程結束,或者到達阻塞時間。
      同時,join()方法的本質是讓在當前線程對象執行個體上調用線程的wait()方法。

    • yield() 方法
      其簽名如下:
      public static native void yield();
      這個靜態方法會使當前線程讓出CPU。(PS:但還是會進行CPU資源的搶奪)

      當某個線程不是那麼重要,或者優先順序別較低,可以在適當時候調用 Thread.yield(),給予其他重要線程更多工作機會。

關於線程操作的補充
  1. sleep() 與 wait() 的區別
    Object.wait() 和 Thread.sleep() 都可以讓線程等待若干時間。除了** wait() 可以被喚醒外,另外一個主要區別:**wait() 方法會釋放目標對象的鎖,而 sleep() 方法不會釋放任何資源。
三、分門別類的管理:線程組

簡單建立一個線程組(代碼來源於《實戰Java高並發程式設計》書中)

public class MyThread implements Runnable {    public static void main(String[] args) {        //建立一個叫"PrintGroup"的線程組        ThreadGroup tg = new ThreadGroup("PrintGroup");        Thread t1 = new Thread(tg, new MyThread(), "T1");   //加入線程組        Thread t2 = new Thread(tg, new MyThread(), "T2");        t1.start();        t2.start();        //由於線程是動態,activeCount()獲得活動線程總數的估算值        System.out.println("活動線程總數 = " + tg.activeCount());        //列印出線程組的線程資訊        tg.list();        //注意這個方法與Thread.stop()方法遇到問題一樣        tg.stop();    }    @Override    public void run() {        String groupAndName = Thread.currentThread().getThreadGroup().getName()                + "---" + Thread.currentThread().getName();        while (true) {            System.out.println("I am " + groupAndName);            try {                Thread.sleep(3000);            } catch (InterruptedException e){                e.printStackTrace();            }        }    }}
四、駐守背景守護線程

守護線程,是系統的守護者,在後台完成系統性的服務,比如記憶體回收線程、JIT線程等。當一個Java應用內只有守護線程時,Java虛擬機器就會自然退出。
將線程設定為守護線程:

        Thread t = new Thread(new MyThread());        t.setDaemon(true);        t.start();

注意:設定守護線程必須線上程start()之前設定,否則會得到一個IllegalThreadStateException,但程式和線程依然可以正常執行,只是線程被當作使用者線程而已。

五、線程優先順序

Java中線程可以有自己的優先順序。由於線程的優先順序調度和底層作業系統有密切關係,在各個平台表現不一,無法精準控制。
在Java中,使用 1 到 10 表示線程優先順序。一般可以使用三個內建的靜態標量表示:

    public final static int MIN_PRIORITY = 1;    public final static int NORM_PRIORITY = 5;    public final static int MAX_PRIORITY = 10;

PS:數字越大,優先順序越高(一般情況)。

六、synchronized 關鍵字

synchronized 關鍵字用法簡單記錄:

  • 指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。
  • 直接作用於執行個體對象:相當於對當前執行個體加鎖,進入同步代碼前要獲得當前執行個體的鎖。
  • 直接作用於靜態方法:相當於對當前類加鎖,進入同步代碼前要獲得當前類的鎖。

synchronized 可以保證線程間的原子性、可見度和有序性(被 synchronized 限制的多個線程是串列執行的)。

七、部分隱秘的錯誤記錄
  • 執行(synchronized)同步代碼的兩個線程指向了不同的Runnable執行個體,即兩個線程使用了兩個不同的鎖。解決方案:將同步代碼修改為靜態(static)方法。
  • 使用線程不安全的容器,如 ArrayList、HashMap 等。解決方案:改用安全執行緒的容器,如 Vector、ConcurrentHashMap。
  • 不變對象加鎖,導致對於臨界區代碼控制出現問題。
    • 不變對象:對象一旦被建立,就不能修改。
      (如,Integer i; i++的本質是建立一個新的Integer對象,並將引用賦值給i,同時這裡涉及到Integer引用的特性)
八、參考資料
  • 《實戰Java高並發程式設計》(葛一鳴 郭超 著)

JAVA並行程式基礎

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.