多線程


創建多線程的兩種方法:繼承Thread類和實現Runnable接口

1.繼承Thread類創建多線程

         Java的線程通過java.lang.Thread類來控制,一個Thread類的對象代表一個線程,而且只能代表一個線程。一段代碼被執行,一定是在某個線程上運行的,代碼與線程密不可分,同一代碼可以與多個線程相關聯。可以通過Thread.currentThread()靜態函數獲得該代碼當前執行的是哪個線程,得到線程對象後又可以調用線程的getName()方法,取出當前線程的名稱字符串。

         要實現多線程,必須編寫一個繼承了Thread類的子類,子類要覆蓋Thread類中的run方法,在子類的run函數中調用相想在新線程上運行的程序代碼。啓動一個新的線程,不是直接調用Thread子類對象的run方法,而是調用Thread子類對象的start方法。

       class TestThread extends Thread{

            public void run(){

                  //填寫方法體

                }

        }

2.使用Runnabule接口創建多線程

     Runnable接口中只有一個run()方法,實現接口的類實現了此run()方法即可。而且,實現了Runnable接口的類還可以作爲參數傳遞給Thread類的構造方法,而此時,這樣創建的線程將調用哪個實現了Runnable接口的類對象的run()方法作爲其運行代碼,而不再調用Thread類中的run方法。

2.兩種創建方式的對比

Runnable接口相對於繼承Thread類的好處:

①適合多個相同程序代碼的線程去處理同意資源的情況,把虛擬CPU(線程)同程序的代碼,數據有效分離,較好的體現了面向對象的設計思想。

②可以避免由於java的單繼承特點帶來的侷限。當我們要將已經繼承了某一個類的子類放入多線程中,由於一個類不能同時有兩個父類,所以只能使用Runnable接口方法。

③有力與程序的健壯性,代碼能給被多個線程共享,代碼與數據是獨立的。

多線程相關概念

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

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

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

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

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

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

該進程中至少一個線程負責java程序的執行。該線程稱爲主線程。

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

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

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

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

步驟:

1,定義類繼承Thread

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

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

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

發現多線程運行結果每一次都不同。

因爲多個線程都獲取cpu的執行權。cpu執行到誰,誰就運行,

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

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

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

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

爲什麼要覆蓋run方法呢?

Thread類用於描述線程。

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

也就是說Thread類中的run方法,用於存儲線程要運行的代碼。

Demo d=new  Demo();創建好一個線程。

d.start();開啓線程並執行該線程的run方法。

d.run();僅僅是對象調用方法,並沒有開啓線程並執行線程。

線程運行狀態:(5種狀態)

凍結:放棄了執行資格,sleeptime);時間到就醒,wait();等待  notify();叫醒(回到臨時狀態)消亡:stop();,run方法結束;

臨時狀態阻塞:具備運行資格但是沒有執行權。

運行:start()

被創建:new

黑馬程序員_JAVA多線程 - zb83583047 - zb83583047的博客

 

線程名稱:

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

默認名稱:Thread-編號  該編號從0開始。

Thread.currentThread():獲取當前線程對象。(爲靜態函數)

getName():獲取線程名稱。

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

局部的變量在每一個線程區域當中都有獨立的一份。

實現runnable接口(創建線程的第二種方式):

步驟:

1,定義類實現Runnable接口

2,覆蓋Runnable接口中的run方法。

將線程要運行的代碼存放在該run方法中。

3,通過Thread類建立線程對象,並將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。

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

因爲,自定義的run方法所屬的對象是Runnable接口的子類對象。

所以要讓線程去執行指定對象的run方法。就必須明確該run方法所屬對象。

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

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

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

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

兩種方式的區別:

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

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

多線程的安全問題(寫多線程的時候一定要考慮安全問題):

出現安全問題的原因:

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

解決方法:

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

線程同步

Java對於多線程的安全問題提供了專業的解決方式。

就是同步代碼塊。(同步可以解決線程安全問題)

synchronized(對象)

{

需要被同步的代碼

}

對象如同鎖。持有鎖的線程可以在同步中執行。

沒有持有鎖的線程即使獲取CPU的執行權,也進不去,因爲沒有獲取鎖。

火車上的衛生間--經典。

同步的前提:

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

2,必須是多個線程使用同一個鎖。

必須保證同步中只能有一個線程在運行。

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

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

如何找問題:

1,明確哪些代碼是多線程運行代碼。

2,明確共享數據。

3,明確多線程運行代碼中哪些語句是操作共享數據的。

同步有2種表現形式:1同步代碼塊,2,同步函數(把synchronized作爲函數的修飾符,被修飾的函數就變成同步函數了)public synchronized void add(int n){}

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

通過該程序進行驗證。使用兩個線程賣票。一個線程在同步代碼塊中。一個線程在同步函數中。都在執行賣票動作。(同步代碼塊中鎖用this,最後證明和同步函數的鎖相同。)如果同步函數被靜態修飾後,使用的鎖是什麼呢?

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

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

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

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

單例設計模式:1,餓漢式2,懶漢式

餓漢式:

class Single

{

private static final Single s=new Single();

private Single(){}

public static Single getInstance()

{

return s;

}

}

懶漢式:(延遲加載,但多線程容易出問題,所以必須加同步代碼塊且爲了提高運行效率要雙重否定。)

class Single

{

private static Single s=null;

private Single(){}

public static Single getInstance()

{

if(s= =null)

{

synchronized(Single.class)

{

if(s= =null)

s=new Single();

}

}

return s;

}

}

多線程--死鎖:

同步中嵌套同步(鎖中鎖,還沒出來,又被同一個鎖鎖了。進不去,出不來。)。舉例:

class Test implements Runnable

{

private boolean flag;

Test(boolean flag)

{

this.flag = flag;

}

public void run()

{

if(flag)

{

while(true)

{

synchronized(MyLock.locka)

{

System.out.println(Thread.currentThread().getName()+"...if locka ");

synchronized(MyLock.lockb)

{

System.out.println(Thread.currentThread().getName()+"..if lockb");

}

}

}

}

else

{

while(true)

{

synchronized(MyLock.lockb)

{

System.out.println(Thread.currentThread().getName()+"..else lockb");

synchronized(MyLock.locka)

{

System.out.println(Thread.currentThread().getName()+".....else locka");

}

}

}

}

}

}

class MyLock

{

static Object locka = new Object();

static Object lockb = new Object();

}

class  DeadLockTest

{

public static void main(String[] args) 

{

Thread t1 = new Thread(new Test(true));

Thread t2 = new Thread(new Test(false));

t1.start();

t2.start();

}

}

多線程間通信

線程間通訊其實就是多個線程在操作同一個資源,但是操作的動作不同。

多線程間通信-等待喚醒機制

r.wait();凍結

r.notify();叫醒(默認叫醒系統中前面凍結的那一個線程)

r.notifyAll();叫醒所有的線程

以上3種方法。都使用在同步中,因爲要對持有監視器(鎖)的線程操作。

所以要使用在同步中,因爲只有同步才具有鎖。

爲什麼這些操作線程的方法,要定義在Object類中呢?

因爲這些方法在操作同步中線程時,都必須要標識它們所操作線程只有的鎖。

只有同一個鎖上的被等待線程,可以被同一個鎖上notify喚醒。

不可以對不同鎖中的線程進行喚醒。

也就是說,等待和喚醒必須是同一個鎖。

而鎖可以是任意對象,所以可以被任意對象調用的方法定義object類中。

JDK1.5中提供了多線程升級解決方案。

將同步Synchronized替換成顯示Lock操作。將Object中的waitnotifyAll替換成了Condition對象。該對象可以通過Lock鎖進行獲取。實現了本方只喚醒對方操作。(ProducerConsumerDemo2)。釋放鎖的特性一定要執行。

1207多線程--停止線程:

stop方法已經過時。如何停止線程?

只有一種,run方法結束。開啓多線程運行,運行代碼通常是循環結構。

只要控制住循環,就可以讓run方法結束,也就是線程結束。

特殊情況:當線程處於了凍結狀態,就不會讀取到標記。那麼線程就不會結束。

當沒有指定方式讓凍結的線程恢復到運行狀態時,這時需要對凍結進行清除。

強制讓線程恢復到運行狀態中來。這樣就可以操作標記讓線程結束。

Thread類中提供了該方法 interrupt();

多線程--守護線程:

當所有的前臺線程結束後,所有的後臺線程(守護線程)會自動結束。

多線程--join方法:

A線程執行到了B線程的.join()(比如B.join())方法時,A就會等待。等B線程都執行完,A纔會執行。

join可以用來臨時加入線程執行。

多線程的同步和線程通信

多線程的同步使用synchronized()語句。synchronized語句的格式爲:

synchronized(object){代碼}   //Object可以使任意的一個對象。

任意類型的對象都有一個標誌位,該標誌位具有0,1兩種狀態,其開始狀態爲1,當執行synchronized(object)語句後,object對象的標誌位變爲0狀態,
知道執行完整個synchronized語句中的代碼後又回到1狀態。要保證synchronized裏面用的是一個鎖旗標,而不是每調用一次run()方法都使用一個新的鎖旗標。如下程序:
<pre name="code" class="java">class ThreadTest implements Runable
{
     private int tickets=100;
     public void run(){
        String str=new String("");
        while(true)
          {
              synchronized(str)
                {
                   if(tickets>0)
                      {
                           try
                              {
                                 Thread.sleep(10);
                              }
                           catch(Exception e)
                             {System.out.println(e.getMessage());}
                   System.out.println(Thread.currentThread().getName()+"is saling ticket" +tickets--);
                      }
                }
           }
     } 

}
這樣調用一次run方法都是使用各自的對象,無法實現進程同步。

方法類型前面加上synchronized可以實現函數同步。在同一類中,使用synchronized關鍵字定義的若干方法,可以再多個線程之間同步,當有一個線程進入synchronized修飾的方法,
即獲得監控器,其他線程就不能進入同一個對象的所有使用了synchronized修飾的方法,直到第一個線程執行完它所進入的synchronized修飾的方法爲止,即離開監視器爲止。
同步函數所用的監視器對象是this。





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