synchronized Lock

synchronized 和Lock區別 :

https://www.jianshu.com/p/36eedeb3f912

https://blog.csdn.net/lengxiao1993/article/details/81568130

https://www.zhihu.com/question/57794716/answer/1229073284

 

  1. 來源: 
    lock是一個接口,而synchronized是java的一個關鍵字,synchronized是內置的語言實現;

  2. 異常是否釋放鎖: 
    synchronized在發生異常時候會自動釋放佔有的鎖,因此不會出現死鎖;而lock發生異常時候,不會主動釋放佔有的鎖,必須手動unlock來釋放鎖,可能引起死鎖的發生。(所以最好將同步代碼塊用try catch包起來,finally中寫入unlock,避免死鎖的發生。)

  3. 是否響應中斷 
    lock等待鎖過程中可以用interrupt來中斷等待,而synchronized只能等待鎖的釋放,不能響應中斷;

  4. 是否知道獲取鎖 
    Lock可以通過trylock來知道有沒有獲取鎖,而synchronized不能;

  5. Lock可以提高多個線程進行讀操作的效率。(可以通過readwritelock實現讀寫分離)

  6. 在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

  7. synchronized使用Object對象本身的wait 、notify、notifyAll調度機制,而Lock可以使用Condition進行線程之間的調度,

2、synchronized和lock性能區別

synchronized是託管給JVM執行的, 
而lock是java寫的控制鎖的代碼。

在Java1.5中,synchronize是性能低效的。因爲這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用Java提供的Lock對象,性能更高一些。

作者:敖丙
鏈接:https://www.zhihu.com/question/57794716/answer/1229073284
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
 

那用戶態和內核態又是啥呢?

Linux系統的體系結構大家大學應該都接觸過了,分爲用戶空間(應用程序的活動空間)和內核。

我們所有的程序都在用戶空間運行,進入用戶運行狀態也就是(用戶態),但是很多操作可能涉及內核運行,比我I/O,我們就會進入內核運行狀態(內核態)。

這個過程是很複雜的,也涉及很多值的傳遞,我簡單概括下流程:

  1. 用戶態把一些數據放到寄存器,或者創建對應的堆棧,表明需要操作系統提供的服務。
  2. 用戶態執行系統調用(系統調用是操作系統的最小功能單位)。
  3. CPU切換到內核態,跳到對應的內存指定的位置執行指令。
  4. 系統調用處理器去讀取我們先前放到內存的數據參數,執行程序的請求。
  5. 調用完成,操作系統重置CPU爲用戶態返回結果,並執行下個指令。

所以大家一直說,1.6之前是重量級鎖,沒錯,但是他重量的本質,是ObjectMonitor調用的過程,以及Linux內核的複雜運行機制決定的,大量的系統資源消耗,所以效率才低。

還有兩種情況也會發生內核態和用戶態的切換:異常事件和外圍設備的中斷 大家也可以瞭解下。

但是到了Java1.6,發生了變化。synchronize在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在Java1.6上synchronize的性能並不比Lock差。官方也表示,他們也更支持synchronize,在未來的版本中還有優化餘地。

2種機制的具體區別: 
synchronized原始採用的是CPU悲觀鎖機制,即線程獲得的是獨佔鎖。獨佔鎖意味着其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起CPU頻繁的上下文切換導致效率很低。

而Lock用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是CAS操作(Compare and Swap)。我們可以進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。這裏其實就是調用的CPU提供的特殊指令。

現代的CPU提供了指令,可以自動更新共享數據,而且能夠檢測到其他線程的干擾,而 compareAndSet() 就用這些代替了鎖定。這個算法稱作非阻塞算法,意思是一個線程的失敗或者掛起不應該影響其他線程的失敗或掛起的算法。

========================================

JVM對synchronized 做的優化

  • 無鎖 偏向鎖,多數情況下,鎖不存在競爭,總是由同一線程獲得。

1 重量級鎖
在上一篇博客中我們知道,Synchronized的實現依賴於與某個對象向關聯的monitor(監視器)實現,而monitor是基於底層操作系統的Mutex Lock實現的,而基於Mutex Lock實現的同步必須經歷從用戶態到核心態的轉換,這個開銷特別大,成本非常高。所以頻繁的通過Synchronized實現同步會嚴重影響到程序效率,而這種依賴於Mutex Lock實現的鎖機制也被稱爲“重量級鎖”,爲了減少重量級鎖帶來的性能開銷,JDK對Synchronized進行了種種優化,尤其是在JDK1.5中引入了輕量級鎖和偏向鎖。

熟悉JVM內存模型的同學都知道,每一個Java實例對象在內存中都包含一部分稱之爲對象頭的區域,對象頭記錄了對象的運行時數據,這部分運行時數據包括GC分代信息和鎖狀態。

鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。

2 輕量級鎖
輕量級鎖是相對於重量級鎖而言在獲取鎖和釋放鎖時更加高效,但輕量級鎖並不能代替重量級鎖。輕量級鎖適用的場景是在線程交替獲取某個鎖執行同步代碼塊的場景,如果出現多個進程同時競爭同一個鎖時,輕量級鎖會膨脹成重量級鎖。

2.1 輕量級鎖加鎖過程
當代碼進入同步塊時,如果同步對象鎖爲無鎖狀態(偏向鎖標識爲“0”,鎖標誌位爲“01”),則當前執行線程會在當前棧幀中建立一個鎖記錄(Lock Record)用於複製同步對象的Mark Word, 稱之爲Displaced Mark Word
系統通過CAS操作將對象的Mark Word更新指向Lock Word,並將同步對象的Owner Thread指定爲當前線程。若果操作成功進入步驟3,失敗進入步驟4
如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標誌位設置爲“00”,即表示此對象處於輕量級鎖定狀態
如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹爲重量級鎖,鎖標誌的狀態值變爲“10”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。 而當前線程便嘗試使用自旋來獲取鎖,自旋就是爲了不讓線程阻塞,而採用循環去獲取鎖的過程
2.2 輕量級鎖的釋放過程
通過CAS操作將Displaced Mark Word替換對象的Mark Word
如果操作成功,同步完成
如果失敗,則說明已經有其他線程競爭當前對象,此時對象的鎖已經升級爲重量級鎖。則當前線程在釋放的同時需要通知其他等待線程
3 偏向鎖
偏向鎖是基於一個經驗爲前提的:在多線程環境下,存在很多同步塊只會被一個線程執行。在這樣的情況下,使用重量級鎖或者輕量鎖都不是最經濟的。因爲輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令。輕量級鎖的機制很簡單,當某個同步對象被某個線程獲取後,同步對象會將其thread owner指向該線程。即使在線程退出同步塊之後,同步對象的thread owner依然不會改變。當該線程下次再次進入該代碼塊時,不用再獲取同步對象使用權而直接執行代碼塊。如果有其他線程競爭該同步對象,則偏向鎖失效,偏向鎖將升級爲輕量級鎖或重量級鎖。

3.1 偏向鎖的獲取
訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否爲01——確認爲可偏向狀態
如果爲可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟(5),否則進入步驟(3)
如果線程ID並未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設置爲當前線程ID,然後執行(5);如果競爭失敗,執行(4)
如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼
執行同步代碼
3.2 偏向鎖的釋放
偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位爲“01”)或輕量級鎖(標誌位爲“00”)的狀態。

3.3 偏向鎖、輕量級鎖、重量級鎖之間的轉換


4 其他優化
4.1 適應性自旋
從輕量級鎖獲取的流程中我們知道,當線程在獲取輕量級鎖的過程中執行CAS操作失敗時,是要通過自旋來獲取重量級鎖的。問題在於,自旋是需要消耗CPU的,如果一直獲取不到鎖的話,那該線程就一直處在自旋狀態,白白浪費CPU資源。解決這個問題最簡單的辦法就是指定自旋的次數,例如讓其循環10次,如果還沒獲取到鎖就進入阻塞狀態。但是JDK採用了更聰明的方式——適應性自旋,簡單來說就是線程如果自旋成功了,則下次自旋的次數會更多,如果自旋失敗了,則自旋的次數就會減少。

4.2 鎖粗化
鎖粗化的概念應該比較好理解,就是將多次連接在一起的加鎖、解鎖操作合併爲一次,將多個連續的鎖擴展成一個範圍更大的鎖。

4.3 鎖消除
鎖消除即刪除不必要的加鎖操作。根據代碼逃逸技術,如果判斷到一段代碼中,堆上的數據不會逃逸出當前線程,那麼可以認爲這段代碼是線程安全的,不必要加鎖。
--------------------- 

原文:https://blog.csdn.net/asiaLIYAZHOU/article/details/76098607 

 

========================================

 

synchronized 對於 普通同步方法,鎖是當前的實例對象,

                       對於靜態同步方法,鎖是當前類的class對象(存放在方法區的class類信息對象  Test.Class)

                       對於同步方法快,鎖是Synchonized括號裏配置的對象。

           

synchronized void method{}功能上,等效於

void method{

   synchronized(this) {

    ...

   }

       synchronized {static方法}此代碼塊等效於

void method{

   synchronized(Obl.class)

   }

 

 

 wait 和notify 是object對象裏的方法,執行了wait()的對象不會再佔用鎖 

// 注意:調用對象的wait()方法時,不會佔用鎖;調用線程的sleep()方法時的時候會佔用鎖
 
package com.threadStudy; /**  * Created by shaxj-mac on 16/6/16.  */ 
public class Interrupt {

    public static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch start = new CountDownLatch(3);
        ThreadClass t1 = new ThreadClass(start);
        ThreadClass t2 = new ThreadClass(start);
        ThreadClass t3 = new ThreadClass(start);

        t1.start();
        t2.start();
        t3.start();

//        t1.interrupt();
        start.await();

        // 如果不加lock會拋出異常
//        IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
//        根據官方註釋解釋,“當前線程不是對象管理者的所有者”
        synchronized (lock) {
            System.out.println("notify start");
            lock.notifyAll();
            System.out.println("notify end");
        }
    }

    public static class ThreadClass extends Thread {
        CountDownLatch start;

        ThreadClass(CountDownLatch start) {
            this.start = start;
        }

        @Override
        public void run() {
            System.out.println(this.getId() + ",run");
            // 注意:wait的時候,不會佔用鎖
            synchronized (lock) {
                try {
                    System.out.println("currentThread:" + this.getName());
                    start.countDown();
                    System.out.println("start.getCount = " + start.getCount());
                    lock.wait();
                    System.out.println("currentThread:" + this.getName() + "alter wait");
//                    sleep(5000);
//                    System.out.println("currentThread:" + this.getName() + "alter sleep");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // TODO 獲取任務對象,跳出synchronized 模塊,否則繼續wait
            }
        }
    }
}
 
 
 
 
 
執行notifyAll的時候 會先執行當前持有lock的對象,等當前程序執行完(釋放鎖後)再執行其他之前wait的對象
思考下面程序中的 (把flag=false放在任意位置對結果沒有任何影響!)
lock.notifyAll(); TimeUnit.SECONDS.sleep(10); flag = false;
調用 lock.notifyAll(); 還是會先執行當前的程序,並不會調用notifyall就會立刻去執行其他wait的程序。
 
官方解釋:notify()或notifyAll()方法調用後,等待線程依舊不會從wait()返回,需要調用notify()或notify()的線程釋放鎖後,等待線程纔有機會從wait()返回。
notifyAll()被移動的線程狀態由WAITING變成BLOCKED。
 
 
package com.threadStudy; import java.util.concurrent.TimeUnit; /**  * Created by shaxj-mac on 16/6/16.  */ public class WaitNotify { static boolean flag = true; static Object lock = new Object(); public static void main(String args[]) throws Exception { Thread waitThread = new Thread(new Wait(), "waitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "notifyThread"); notifyThread.start(); } static class Wait implements Runnable { @Override  public void run() { synchronized (lock) { while (flag) { try { System.out.println(Thread.currentThread() + "flag is true.wait@"); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread() + "flag is false. running @"); } } } static class Notify implements Runnable { @Override  public void run() { synchronized (lock) { try { System.out.println(Thread.currentThread() + "hold lock.notify @"); lock.notifyAll(); TimeUnit.SECONDS.sleep(10); flag = false; Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lock) { System.out.println(Thread.currentThread() + " hold lock again . sleep @"); } } } } 

 

 

 

 

 

sleep 是Thread 中的方法。    

 

 volatile 可以用來修飾成員變量,表示當線程使用被volatile修飾的變量時都會強迫cpu從共享內存中取值,而不是從每個線程的緩存中獲取。(共享內存可以理解爲 web orm模型中的數據庫) 

     synchronized

 

       thread.setPriority(Thread.MAX_PRIORITY) 設置線程的優先級 範圍從1-10 默認是5 ,優先級高的分配的時間片要多。設置優先級時,針對頻繁阻塞(休眠或者I/o操作)的線程需要設置較高的優先級,偏重計算的(需要較多的cpu時間)的線程設置爲較低的優先級,確保處理器不會被獨佔。但是操作系統可能會對java中設置的優先級完全不理會。

 

 

* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.

wait /sleep 狀態的thread 不能使用interrupt 方法,如果使用則會拋出 InterruptedException異常

 

 

 

 

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