Java對多線程的支援(一) – Thread類、Runnable介面、Daemon線程、線程同步

來源:互聯網
上載者:User

Java是第一個在語言級提供了對多線程程式設計支援的語言。語言級支援是啥意思呢,就是拋開作業系統,只使用Java內建的語句就能實現多線程。一般來講,多線程會依賴作業系統,我們要高用與系統相關的API才能建立多個線程,如在Windows中的CreateThread()函數。但JAVA就不用了。JAVA運行時系統實現了一個用於調用線程執行的線程調度器,用來決定在某一時刻哪個線程在CPU上運行。

在java技術中,線程通常是搶佔式的而不需要時間片分配進程(分配給每個線程相等的CPU時間的進程)。搶佔式調度模型就是許多線程處於可以運行狀態(等待狀態),但實際上只有一個線程在運行。該線程一直運行到它終止進入可運行狀態(等待狀態),或者另一個具有更高優先順序的線程變成可運行狀態。在後一種情況下,低優先順序的線程被高優先順序的線程搶佔,高優先順序的線程獲得啟動並執行機會。線程調度器支援不同優先順序線程的搶先方式,但其本身不支援相同優先順序線程的時間片輪換。若要想讓其支援,則必須將運行時系統運行在支援時間片輪換作業系統上。

1、Thread類和Runnable介面

在Java中實現多線程可以有2種方式,一個是讓類繼承Thread類,另一個是實現Runnable介面。由於Java不允許多繼承,所以當我們讓自訂的類繼承了Thread類後就不能再繼承其它類了,這樣會降低日後程式的可擴充性。所以一般我們都是採用實現Runnable介面的方法讓一個類具有可執行檔特性。

Runnable介面只有一個方法run(),該線程啟動時,JVM就會調用run()方法以執行該線程。線程類建立後,必須調用start()方法才能啟動線程。如:

package cls;public class ThreadDemo{    public static void main(String[] args)    {        //建立線程類對象並實現 Runnable介面        Thread th1 = new Thread(new Runnable()        {            // 實現Runnable中的run()方法            public void run()            {                while(true)                    System.out.println("Thread1 is running !");            }        });        Thread th2 = new Thread(new Runnable()        {            public void run()            {                while(true)                    System.out.println("Thread2 is running !");            }        });                // 調用start()方法啟動線程        th1.start();        th2.start();    }}

2、Daemon線程

一個Daemon線程指的是一個在後台執行服務的線程。在執行上例代碼時,我們發現,當主線程(main函數)執行完th2.start()這一行的時候,主線程的代碼就已經全部執行完,所以主線程退出了。但我們看到自己建立的th1和th2兩個線程仍然在不停地執行,並沒有退出。如果我們想讓建立的線程在主函數退出後一同退出,那麼就應當將我們建立的線程設定為daemon.Daemon線程當在所有的非Daemon線程都結束後自動結束。

讓我們修改一下上例:

package cls;public class ThreadDemo{    public static void main(String[] args)    {        //建立線程類對象並實現 Runnable介面        Thread th1 = new Thread(new Runnable()        {            // 實現Runnable中的run()方法            public void run()            {                while(true)                    System.out.println("Thread1 is running !");            }        });        Thread th2 = new Thread(new Runnable()        {            public void run()            {                while(true)                    System.out.println("Thread2 is running !");            }        });                // 設定成daemon線程        th1.setDaemon(true);        th2.setDaemon(true);                // 調用start()方法啟動線程        th1.start();        th2.start();                // 主函數睡眠一小會        try        {            Thread.currentThread().sleep(200);        }        catch(Exception e)        {            System.out.println(e.toString());        }    }}

運行後可以看到,200ms後主線程退出,相應的th1和th2也退出了。

3、線程同步

假設對象A有兩個屬性a和b,另有一個對象B有屬性a和b,並且我們要用對象B來更新對象A。當這種更新操作完成後(對象A的a,b屬性值變成了對象B的a,b屬性值),則稱A與B同步。

如果對象B在更新對象A時(還沒更新完),又來了個對象C,對象C搶在B之前更新了A,則稱A與B不同步。

討論區對話就不得不考慮線程同步的問題。當多個線程共用同一個資料時,容易造成資料的不同步。而這種不同步引發的錯誤往往難以修正,常常是程式在執行了幾千次、幾萬次甚至是幾年才會出現。因此線程同步就顯得尤為重要。

下面寫一個類比火車站賣票的例子,故意使得線程不同步,看看會發生什麼問題:

package cls;public class ThreadSynchronized{    public static void main(String[] args)    {        Ticket tk = new Ticket();                Thread th1 = new Thread(new Sold(tk));        Thread th2 = new Thread(new Sold(tk));                th1.start();        th2.start();    }}class Ticket{    int amount = 100; // 有100張票        // 賣出一張票,總票數減1,返回被賣出的票號    public int sold()    {        return amount--;    }    // 返回當前剩餘票數    public int getAmount()    {        return amount;    }}// 售票線程class Sold implements Runnable{    private Ticket tk;        public Sold1(Ticket tk)    {        this.tk = tk;    }    public void run()    {        while(tk.getAmount() > 0) // 還有餘票        {            try            {                Thread.currentThread().sleep(10); // 睡眠一小會            }            catch(Exception e)            {                System.out.println(e.toString());            }                        System.out.println("Thread1 sold " + tk.sold());        }    }}

執行:

我們發現,Thread1這個線程居然賣出了0這張不存在的票。售票線程在賣票前已經做了

tk.getAmount() > 0

的判斷了,為什麼還會賣出0這張票呢?

我們來分析下程式執行的過程:

當還剩下1這張票時,Thread1用getAmout()判斷出票數大於0,則進入while迴圈進行售票。在還沒有賣之前遇到了sleep(),於是Thread1會放棄執行機會,進入阻塞狀態;這時輪到Thread2執行了,由於Thead1還沒有把1賣出,因此Thread2用getAmout()的結果仍然大於0,所以就把1這張票賣出去了。這時候Thread1睡醒了,則繼續從sleep()這行開始執行,而這時候票數已經變成了0,那麼Thead1自然就會賣出0這張票了。

為了避免此類情況的發生,我們可以使用synchronized關鍵字來進行線程的同步。

synchronized關鍵字可以用在方法上,也可以用在語句塊中。我們可以形象地把synchronized想象成一把鎖。當程式執行到synchronized語句塊(以後稱為同步塊)時,會將這個同步塊的代碼“上鎖”,這時如果我們調用了sleep()方法或者恰好CPU分配的時間片用光了,另一個線程如果也要執行這段代碼時,由於先前的線程已經對這個同步塊進行了“上鎖”,因此這個線程就不能執行同步塊中的代碼,只能等待,直到它的時間片也用完為止。這時前一個線程又得到了執行的機會,執行完代碼塊,它會將這個代碼塊“解鎖”,這樣別的線程才可以執行這個代碼塊。也就是說,一個同步塊在一個時間段內只能被一個特定的線程執行,在這個線程沒有執行完代碼塊的語句之前,其它線程是沒有辦法再執行這個塊中的代碼的。Java是通過請求一個對象的監視器實現這種功能的。一個對象只有一個監視器,當一個線程請求到後,另一個線程在這個線程歸還(執行完同步塊)監視器之前是無法請求到該對象的監視器的。

現在我們來改寫上面的例子:

package cls;class ThreadSynchronized  {      public static void main(String[] args)      {          Ticket tk = new Ticket();          Sold sd = new Sold(tk);                    Thread th1 = new Thread(sd);          Thread th2 = new Thread(sd);                    th1.start();          th2.start();      }  }    class Ticket  {      int amount = 100; // 有100張票            // 賣出一張票,總票數減1,返回被賣出的票號      public int sold()      {          return amount--;      }      // 返回當前剩餘票數      public int getAmount()      {          return amount;      }  }    // 售票線程  class Sold implements Runnable  {      private Ticket tk;            public Sold(Ticket tk)      {          this.tk = tk;      }      public void run()      {                    while(true)          {              // 同步語句塊              synchronized(this)//請求 this對象的監視器              {                  if(tk.getAmount() > 0)                  {                      try                      {                          Thread.currentThread().sleep(1); // 睡眠一小會                      }                      catch(Exception e)                      {                          System.out.println(e.toString());                      }                                            System.out.println(Thread.currentThread().getName() + " sold " + tk.sold());                  }              }          }      }  } 

可以看到,問題就解決了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.