Java鎖機制瞭解一下

前言

回顧前面:html

只有光頭才能變強!java

本文章主要講的是Java多線程加鎖機制,有兩種:編程

  • Synchronized
  • 顯式Lock

不得不嘮叨幾句:c#

  • 在《Java核心技術卷 一》是先講比較難的顯式Lock,而再講的是比較簡單的Synchronized
  • 而《Java併發編程實戰》在前4章零散地講解了Synchronized,將顯式Lock放到了13章

其實都比較坑,若是能先系統講了Synchronized鎖機制,接着講顯式Lock鎖機制,那就很容易理解了。也不須要跨那麼多章節。安全

那麼接下來咱們就開始吧~微信

1、synchronized鎖

1.1synchronized鎖是什麼?

synchronized是Java的一個關鍵字,它可以將代碼塊(方法)鎖起來多線程

  • 它使用起來是很是簡單的,只要在代碼塊(方法)添加關鍵字synchronized,便可以實現同步的功能~
public synchronized void test() {
        // 關注公衆號Java3y
        // doSomething
    }

複製代碼

synchronized是一種互斥鎖併發

  • 一次只能容許一個線程進入被鎖住的代碼塊

synchronized是一種內置鎖/監視器鎖post

  • Java中每一個對象都有一個內置鎖(監視器,也能夠理解成鎖標記),而synchronized就是使用**對象的內置鎖(監視器)**來將代碼塊(方法)鎖定的!

1.2synchronized用處是什麼?

  • synchronized保證了線程的原子性。(被保護的代碼塊是一次被執行的,沒有任何線程會同時訪問)
  • synchronized還保證了可見性。(當執行完synchronized以後,修改後的變量對其餘的線程是可見的)

Java中的synchronized,經過使用內置鎖,來實現對變量的同步操做,進而實現了對變量操做的原子性和其餘線程對變量的可見性,從而確保了併發狀況下的線程安全。性能

1.3synchronized的原理

咱們首先來看一段synchronized修飾方法和代碼塊的代碼:

public class Main {
	//修飾方法
    public synchronized void test1(){

    }

	
    public void test2(){
		// 修飾代碼塊
        synchronized (this){

        }
    }
}
複製代碼

來反編譯看一下:

同步代碼塊

  • monitorenter和monitorexit指令實現的

同步方法(在這看不出來須要看JVM底層實現)

  • 方法修飾符上的ACC_SYNCHRONIZED實現。

synchronized底層是是經過monitor對象,對象有本身的對象頭,存儲了不少信息,其中一個信息標示是被哪一個線程持有

具體可參考:

1.4synchronized如何使用

synchronized通常咱們用來修飾三種東西:

  • 修飾普通方法
  • 修飾代碼塊
  • 修飾靜態方法

1.4.1修飾普通方法:

用的鎖是Java3y對象(內置鎖)

public class Java3y {


    // 修飾普通方法,此時用的鎖是Java3y對象(內置鎖)
    public synchronized void test() {
        // 關注公衆號Java3y
        // doSomething
    }

}

複製代碼

1.4.2修飾代碼塊:

用的鎖是Java3y對象(內置鎖)--->this

public class Java3y {
    
    public void test() {
        
        // 修飾代碼塊,此時用的鎖是Java3y對象(內置鎖)--->this
        synchronized (this){
            // 關注公衆號Java3y
            // doSomething
        }
    }
}
複製代碼

固然了,咱們使用synchronized修飾代碼塊時未必使用this,還能夠使用其餘的對象(隨便一個對象都有一個內置鎖)

因此,咱們能夠這樣幹:

public class Java3y {


    // 使用object做爲鎖(任何對象都有對應的鎖標記,object也不例外)
    private Object object = new Object();


    public void test() {

        // 修飾代碼塊,此時用的鎖是本身建立的鎖Object
        synchronized (object){
            // 關注公衆號Java3y
            // doSomething
        }
    }

}

複製代碼

上面那種方式(隨便使用一個對象做爲鎖)在書上稱之爲-->客戶端鎖,這是不建議使用的

書上想要實現的功能是:給ArrayList添加一個putIfAbsent(),這須要是線程安全的。

假定直接添加synchronized是不可行的

使用客戶端鎖,會將當前的實現與本來的list耦合了

書上給出的辦法是使用組合的方式(也就是裝飾器模式)

1.4.3修飾靜態方法

獲取到的是類鎖(類的字節碼文件對象):Java3y.class

public class Java3y {

    // 修飾靜態方法代碼塊,靜態方法屬於類方法,它屬於這個類,獲取到的鎖是屬於類的鎖(類的字節碼文件對象)-->Java3y.class
    public synchronized void test() {

        // 關注公衆號Java3y
        // doSomething
    }
}
複製代碼

1.4.4類鎖與對象鎖

synchronized修飾靜態方法獲取的是類鎖(類的字節碼文件對象),synchronized修飾普通方法或代碼塊獲取的是對象鎖。

  • 它倆是不衝突的,也就是說:獲取了類鎖的線程和獲取了對象鎖的線程是不衝突的
public class SynchoronizedDemo {

    //synchronized修飾非靜態方法
    public synchronized void function() throws InterruptedException {
        for (int i = 0; i <3; i++) {
            Thread.sleep(1000);
            System.out.println("function running...");
        }
    }
    //synchronized修飾靜態方法
    public static synchronized void staticFunction() throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000);
            System.out.println("Static function running...");
        }
    }

    public static void main(String[] args) {
        final SynchoronizedDemo demo = new SynchoronizedDemo();

        // 建立線程執行靜態方法
        Thread t1 = new Thread(() -> {
            try {
                staticFunction();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 建立線程執行實例方法
        Thread t2 = new Thread(() -> {
            try {
                demo.function();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 啓動
        t1.start();
        t2.start();
    }
}
複製代碼

結果證實:類鎖和對象鎖是不會衝突的

1.5重入鎖

咱們來看下面的代碼:

public class Widget {

	// 鎖住了
	public synchronized void doSomething() {
		...
	}
}

public class LoggingWidget extends Widget {

	// 鎖住了
	public synchronized void doSomething() {
		System.out.println(toString() + ": calling doSomething");
		super.doSomething();
	}
}
複製代碼
  1. 當線程A進入到LoggingWidget的doSomething()方法時,此時拿到了LoggingWidget實例對象的鎖
  2. 隨後在方法上又調用了父類Widget的doSomething()方法,它又是被synchronized修飾
  3. 那如今咱們LoggingWidget實例對象的鎖尚未釋放,進入父類Widget的doSomething()方法還須要一把鎖嗎?

不須要的!

由於鎖的持有者是「線程」,而不是「調用」。線程A已是有了LoggingWidget實例對象的鎖了,當再須要的時候能夠繼續**「開鎖」**進去的!

這就是內置鎖的可重入性

1.6釋放鎖的時機

  1. 當方法(代碼塊)執行完畢後會自動釋放鎖,不須要作任何的操做。
  2. 當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放
  • 不會因爲異常致使出現死鎖現象~

2、Lock顯式鎖

2.1Lock顯式鎖簡單介紹

Lock顯式鎖是JDK1.5以後纔有的,以前咱們都是使用Synchronized鎖來使線程安全的~

Lock顯式鎖是一個接口,咱們來看看:

隨便翻譯一下他的頂部註釋,看看是幹嗎用的:

能夠簡單歸納一下:

  • Lock方式來獲取鎖支持中斷、超時不獲取、是非阻塞的
  • 提升了語義化,哪裏加鎖,哪裏解鎖都得寫出來
  • Lock顯式鎖能夠給咱們帶來很好的靈活性,但同時咱們必須手動釋放鎖
  • 支持Condition條件對象
  • 容許多個讀線程同時訪問共享資源

2.2synchronized鎖和Lock鎖使用哪一個

前面說了,Lock顯式鎖給咱們的程序帶來了不少的靈活性,不少特性都是Synchronized鎖沒有的。那Synchronized鎖有沒有存在的必要??

必須是有的!!Lock鎖在剛出來的時候不少性能方面都比Synchronized鎖要好,可是從JDK1.6開始Synchronized鎖就作了各類的優化(畢竟親兒子,牛逼)

因此,到如今Lock鎖和Synchronized鎖的性能其實差異不是很大!而Synchronized鎖用起來又特別簡單。Lock鎖還得顧忌到它的特性,要手動釋放鎖才行(若是忘了釋放,這就是一個隱患)

因此說,咱們絕大部分時候仍是會使用Synchronized鎖,用到了Lock鎖說起的特性,帶來的靈活性纔會考慮使用Lock顯式鎖~

2.3公平鎖

公平鎖理解起來很是簡單:

  • 線程將按照它們發出請求的順序來獲取鎖

非公平鎖就是:

  • 線程發出請求的時能夠**「插隊」**獲取鎖

Lock和synchronize都是默認使用非公平鎖的。若是不是必要的狀況下,不要使用公平鎖

  • 公平鎖會來帶一些性能的消耗的

4、最後

本文講了synchronized內置鎖和簡單描述了一下Lock顯式鎖,總得來講:

  • synchronized好用,簡單,性能不差
  • 沒有使用到Lock顯式鎖的特性就不要使用Lock鎖了。

Lock鎖這裏只是介紹了一些些,明天會詳解它的相關子類和須要注意的地方,敬請期待~

以前在學習操做系統的時候根據《計算機操做系統-湯小丹》這本書也作了一點點筆記,都是比較淺顯的知識點。或許對你們有幫助

參考資料:

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y

文章的目錄導航

相關文章
相關標籤/搜索