同步,線程通信,lock

同步

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

弊端:相對降低性能,因爲判斷鎖需要消耗資源,產生了死鎖。

定義同步是有前提的:

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

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

同步的第二種表現形式:

同步函數:其實就是將同步關鍵字定義在函數上,讓函數具備了同步性。

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

通過驗證,函數都有自己所屬的對象this,所以同步函數所使用的鎖就是this鎖。

當同步函數被static修飾時,這時的同步用的是哪個鎖呢?

靜態函數在加載時所屬於類,這時有可能還沒有該類產生的對象,但是該類的字節碼文件加載進內存就已經被封裝成了對象,這個對象就是該類的字節碼文件對象。

所以靜態加載時,只有一個對象存在,那麼靜態同步函數就使用的這個對象。

這個對象就是 類名.class

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

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

同步函數使用的鎖是this,靜態同步函數的鎖是該類的字節碼文件對象。

在一個類中只有一個同步,可以使用同步函數。如果有多同步,必須使用同步代碼塊,來確定不同的鎖。所以同步代碼塊相對靈活一些。

//懶漢式:延遲加載方式。

當多線程訪問懶漢式時,因爲懶漢式的方法內對共性數據進行多條語句的操作。所以容易出現線程安全問題。爲了解決,加入同步機制,解決安全問題。但是卻帶來了效率降低。

爲了效率問題,通過雙重判斷的形式解決。

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;

    }

}

同步死鎖:通常只要將同步進行嵌套,就可以看到現象。同步函數中有同步代碼塊,同步代碼塊中還有同步函數。

線程間通信:思路:多個線程在操作同一個資源,但是操作的動作卻不一樣。

1:將資源封裝成對象。

2:將線程執行的任務(任務其實就是run方法。)也封裝成對象。

等待喚醒機制:涉及的方法:

wait:將同步中的線程處於凍結狀態。釋放了執行權,釋放了資格。同時將線程對象存儲到線程池中。

notify:喚醒線程池中某一個等待線程。

notifyAll:喚醒的是線程池中的所有線程。

注意:

1:這些方法都需要定義在同步中。

2:因爲這些方法必須要標示所屬的鎖。

    你要知道 A鎖上的線程被wait了,那這個線程就相當於處於A鎖的線程池中,只能A鎖的notify喚醒。

3:這三個方法都定義在Object類中。爲什麼操作線程的方法定義在Object類中?

    因爲這三個方法都需要定義同步內,並標示所屬的同步鎖,既然被鎖調用,而鎖又可以是任意對象,那麼能被任意對象調用的方法一定定義在Object類中。

wait和sleep區別: 分析這兩個方法:從執行權和鎖上來分析:

wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。

sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。

wait:線程會釋放執行權,而且線程會釋放鎖。

Sleep:線程會釋放執行權,但不是不釋放鎖。

線程的停止:通過stop方法就可以停止線程。但是這個方式過時了。

停止線程:原理就是:讓線程運行的代碼結束,也就是結束run方法。

怎麼結束run方法?一般run方法裏肯定定義循環。所以只要結束循環即可。

第一種方式:定義循環的結束標記。

第二種方式:如果線程處於了凍結狀態,是不可能讀到標記的,這時就需要通過Thread類中的interrupt方法,將其凍結狀態強制清除。讓線程恢復具備執行資格的狀態,讓線程可以讀到標記,並結束。

---------< java.lang.Thread >----------

interrupt():中斷線程。

setPriority(int newPriority):更改線程的優先級。

getPriority():返回線程的優先級。

toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。

Thread.yield():暫停當前正在執行的線程對象,並執行其他線程。

setDaemon(true):將該線程標記爲守護線程或用戶線程。將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java虛擬機退出。該方法必須在啓動線程前調用。

join:臨時加入一個線程的時候可以使用join方法。

當A線程執行到了B線程的join方式。A線程處於凍結狀態,釋放了執行權,B開始執行。A什麼時候執行呢?只有當B線程運行結束後,A才從凍結狀態恢復運行狀態執行。

Lock接口:多線程在JDK1.5版本升級時,推出一個接口Lock接口。

解決線程安全問題使用同步的形式,(同步代碼塊,要麼同步函數)其實最終使用的都是鎖機制。

到了後期版本,直接將鎖封裝成了對象。線程進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。

在後期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,並把鎖定義成對象。

所以同步是隱示的鎖操作,而Lock對象是顯示的鎖操作,它的出現就替代了同步。

在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因爲同步中的鎖是任意對象,所以操作鎖的等待喚醒的方法都定義在Object類中。

而現在鎖是指定對象Lock。所以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中並沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝。並提供了功能一致的方法 await()、signal()、signalAll()體現新版本對象的好處。

< java.util.concurrent.locks >Condition接口:await()、signal()、signalAll();

class BoundedBuffer {

   final Lock lock = new ReentrantLock();

   final Condition notFull  = lock.newCondition();

   final Condition notEmpty = lock.newCondition();

   final Object[] items = new Object[100];

   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {

     lock.lock();

     try {

       while (count == items.length)

         notFull.await();

       items[putptr] = x;

       if (++putptr == items.length) putptr = 0;

       ++count;

       notEmpty.signal();

     }

    finally {

       lock.unlock();

     }

   }

   public Object take() throws InterruptedException {

     lock.lock();

     try {

       while (count == 0)

         notEmpty.await();

       Object x = items[takeptr];

       if (++takeptr == items.length) takeptr = 0;

       --count;

       notFull.signal();

       return x;

     }

finally {

       lock.unlock();

     }

   }

 }

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