多線程

---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------

 

多線程

        進程:是一個正在執行中的程序。

        每一個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。

        線程:就是進程中的一個獨立的控制單元。

        線程在控制着進程的執行。

        一個進程中至少有一個線程。

 

        Java VM  啓動的時候會有一個進程java.exe.

        該進程中至少一個線程負責java程序的執行。而且這個線程運行的代碼存在於main方法中。該線程稱之爲主線程。

        擴展:其實更細節說明jvm,jvm啓動不止一個線程,還有負責垃圾回收機制的線程。

        如何在自定義的代碼中,自定義一個線程呢?

            通過對api的查找,java已經提供了對線程這類事物的描述。就Thread類。

            創建線程的第一種方式:繼承Thread類。

        步驟:

            1,定義類繼承Thread

            2,複寫Thread類中的run方法。

               目的:將自定義代碼存儲在run方法,讓線程運行。

            3,調用線程的start方法,該方法兩個作用:啓動線程,調用run方法。

               發現運行結果每一次都不同。因爲多個線程都獲取cpu的執行權。cpu執行到誰,誰就運行。

               明確一點,在某一個時刻,只能有一個程序在運行。(多核除外)

               cpu在做着快速的切換,以達到看上去是同時運行的效果。

        我們可以形象把多線程的運行行爲在互相搶奪cpu的執行權。

        這就是線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說的算。

 

        爲什麼要覆蓋run方法呢?

            Thread類用於描述線程。

            該類就定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是run方法。也就是說Thread類中的run方法,用於存儲線程要運行的       代碼。

        原來線程都有自己默認的名稱。

        Thread-編號 該編號從0開始。

        static Thread currentThread():獲取當前線程對象。

        getName(): 獲取線程名稱。

        設置線程名稱:setName或者構造函數。

             sleep方法需要指定睡眠時間,單位是毫秒。

             一個特殊的狀態:就緒。具備了執行資格,但是還沒有獲取資源。

            如:

  1. class Ticket implements Runnable//extends Thread  
  2. {  
  3.     private  int tick = 100;  
  4.     public void run()  
  5.     {  
  6.         while(true)  
  7.         {  
  8.             if(tick>0)  
  9.             {<span style="white-space:pre"> </span>try{Thread.sleep(10);}  catch(Exception e){}//能看出會發生線程問題  
  10.                 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  
  11.             }  
  12.         }  
  13.     }  
  14. }  
  15. class  TicketDemo  
  16. {  
  17.     public static void main(String[] args)   
  18.     {  
  19.   
  20.         Ticket t = new Ticket();  
  21.   
  22.         Thread t1 = new Thread(t);//創建了一個線程;  
  23.         Thread t2 = new Thread(t);//創建了一個線程;  
  24.         Thread t3 = new Thread(t);//創建了一個線程;  
  25.         Thread t4 = new Thread(t);//創建了一個線程;  
  26.         t1.start();  
  27.         t2.start();  
  28.         t3.start();  
  29.         t4.start();  
  30.     }  
  31. }  

 

Thread類的特點:

       1、當類去描述事物,事物中有屬性和行爲。如果行爲中有部分代碼需要被多線程所執行,同時還在操作屬性。就需要該類繼承Thread類,產生該類的對象作爲線程對象。可是這樣做會導致每一個對象中都存儲一份屬性數據。無法在多個線程中共享該數據。可以加上靜態,雖然實現了共      享,但是生命週期過長。

       2、如果一個類明確了自己的父類,那麼它就不可以在繼承Thread。或者一個類繼承了Thread後也同樣不能繼承別的類,因爲java不允許類的多      繼承。

 

     實現Runnable接口:

             1、子類覆蓋接口中的run方法。

             2、通過Thread類創建線程,並將實現了Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數。

             3Thread類對象調用start方法開啓線程。

 

             Runnable的特點:

                    1.描述事物的類中封裝了屬性和行爲,如果有部分代碼需要被多線程所執行。同時還在操作屬性。那麼可以通過實現Runnable接口的              方式。因爲該方式是定義一個Runnable接口的子類對象,可以被多個線程所操作實現了數據的共享。

                    2.實現了Runnable接口的好處,避免了單繼承的侷限性。也就說,一個類如果已經有了自己的父類是不可以繼承Thread類的。但是該              類中還有需要被多線程執行的代碼。這時就可以通過在該類上功能擴展的形式。實現一個Runnable接口。

                    所以綜上說述:強烈建議用第二種方式!!!

 

        需求:簡單的賣票程序,多個窗口同時買票。

              創建線程的第二種方式:實現Runable接口。

              步驟:

                1,定義類實現Runnable接口

                2,覆蓋Runnable接口中的run方法,將線程要運行的代碼存放在該run方法中。

                3,通過Thread類建立線程對象。

                4,將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。

                  爲什麼要將Runnable接口的子類對象傳遞給Thread的構造函數。

                   因爲,自定義的run方法所屬的對象是Runnable接口的子類對象。所以要讓線程去指定指定對象的run方法。就必須明確該run                    方法所屬對象。

                5,調用Thread類的start方法開啓線程並調用Runnable接口子類的run方法。

               實現方式和繼承方式有什麼區別呢?

                   實現方式好處:避免了單繼承的侷限性。

                 在定義線程時,建立使用實現方式。

 

        兩種方式區別:

            繼承Thread:線程代碼存放Thread子類run方法中。

            實現Runnable,線程代碼存在接口的子類的run方法。

        如:

1.  class Ticket implements Runnable  

2.  {  

3.      private  int tick = 1000;  

4.      Object obj = new Object();  

5.      public void run()  

6.      {  

7.          while(true)  

8.          {  

9.              synchronized(obj)//定義同步鎖  

10.              {  

11.                  if(tick>0)  

12.                  {  

13.                      //try{Thread.sleep(10);}catch(Exception e){}  

14.                      System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  

15.                  }  

16.              }  

17.          }  

18.      }  

19.  }  

20.  class  TicketDemo2  

21.  {  

22.      public static void main(String[] args)   

23.      {  

24.    

25.          Ticket t = new Ticket();  

26.          Thread t1 = new Thread(t);  

27.          Thread t2 = new Thread(t);  

28.          Thread t3 = new Thread(t);  

29.          Thread t4 = new Thread(t);  

30.          t1.start();  

31.          t2.start();  

32.          t3.start();  

33.          t4.start();  

34.      }  

35. 

 

       同步代碼塊:

        通過分析,發現,打印出0,-1,-2等錯票。多線程的運行出現了安全問題。

        問題的原因:

            當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數           據的錯誤。

        解決辦法:

            對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。

        Java對於多線程的安全問題提供了專業的解決方式,就是同步代碼塊。

            synchronized(對象){

              需要被同步的代碼

            }

        對象如同鎖,持有鎖的線程可以在同步中執行。沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖。

        同步的前提:

            1,必須要有兩個或者兩個以上的線程。

            2,必須是多個線程使用同一個鎖。必須保證同步中只能有一個線程在運行。

        好處:解決了多線程的安全問題。

        弊端:多個線程需要判斷鎖,較爲消耗資源。

 

    同步函數和同步代碼塊的區別:

             1、同步代碼塊使用的鎖可以是任意對象。

             2、同步函數使用的鎖是固定對象 this

             所以一般定義同步時,建議使用同步代碼塊.當然,如果對象可以使用this。那麼可以簡化同步函數的形式。

        同步函數用的是哪一個鎖呢?

            函數需要被對象調用,那麼函數都有一個所屬對象引用,就是this。所以同步函數使用的鎖是this。

        如果同步函數被靜態修飾後,使用的鎖是什麼呢?

            通過驗證,發現不在是this。因爲靜態方法中也不可以定義this。

            靜態進內存是,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。

            類名.class  該對象的類型是Class

            靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象。 類名.class

 

---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章