Synchronized_詳細解析

使用場景

public synchronized void test2(){
    log.info("實例方法");
}

public synchronized static void test3(){
    log.info("靜態方法");
}

public void test4(){
    log.info("代碼塊");
}

引出兩個小問題:

一個類中同時有synchronized static方法和synchronized的方法,這兩個方法同步嗎?

不同步

  • 靜態方法與成員方法的區別是,靜態方法歸屬類,成員方法歸屬於對象
  • synchronized方法鎖定的是當前對象
  • 如果是靜態同步方法,鎖定的是類的Class對象。注意這裏不是鎖定此類的所有對象,僅是唯一的Class對象。
  • 如果是普通同步方法,鎖定的是調用該方法的那個對象。

下面是我之前測試的代碼,如果兩個方法是同步的話,結果應該是一個線程鎖定對象循環輸出完釋放對象後,另一個線程再循環輸出,但實際上是兩個線程交替執行的,所以這兩個方法不同步。

public class Test {
	
	public static void main(String[] args) {
		Test test=new Test();
		test.new T1().start();
		test.new T2().start();
	}
	
	public synchronized static void func1() {
		for(int i=0;i<1000;i++) {
			System.out.println("this is a synchronized static method");
		}
	}
	
	public synchronized void func2() {
		for(int i=0;i<1000;i++) {
			System.out.println("this is a synchronized method");
		}
	}
	
	class T1 extends Thread{
		@Override
		public void run() {
			func1();
		}
	}
	class T2 extends Thread{
		@Override
		public void run() {
			func2();
		}
	}
}

當一個線程進入一個對象的synchronized方法後,其他線程是否可以進入此對象的其他方法?

  • 其他方法如果是synchronized方法
    • 如果當前synchronized方法中沒有調用對象的wait方法,則其他線程不可以進入
    • 如果當前synchronized方法中調用了對象的wait方法,則其他線程可以進入
  • 其他方法如果是普通的成員方法或者static方法,則其他線程可以進入,因爲未用synchronized修飾的方法意味着不需要數據同步。
  • 其他方法如果是synchronized static方法,則其他線程可以進入,因爲因爲synchronized成員方法同步鎖定的是當前方法所屬實例對象,synchronized static方法同步鎖定的是當前類的Class對象,並非同一個對象。

特性

JVM中對象構成

對象頭

  • Mark Word(標記字段):默認存儲對象的HashCode,分代年齡和鎖標誌位信息。它會根據對象的狀態複用自己的存儲空間,也就是說在運行期間Mark Word裏存儲的數據會隨着鎖標誌位的變化而變化。
  • Klass Point(類型指針):對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

實例數據

存放類的數據信息,父類的信息。

對齊填充

由於虛擬機要求對象起始地址必須是8字節的整數倍,填充數據不是必須存在的,僅僅是爲了字節對齊。

JMM內存模型

https://blog.csdn.net/Chill_Lyn/article/details/106056359

可見性、原子性、有序性

可重入性

synchronized鎖對象的時候有個計數器,他會記錄下線程獲取鎖的次數,在執行完對應的代碼塊之後,計數器就會-1,直到計數器清零,就釋放鎖了。

不可中斷性

一個線程獲取鎖之後,另外一個線程處於阻塞或者等待狀態,前一個不釋放,後一個也一直會阻塞或者等待,不可以被中斷。

底層實現

測試代碼

public class SynchronizedTest {

	//同步方法
    public synchronized void test1(){
    	//同步代碼塊
        synchronized (Object.class){
            System.out.println(123);
        }
    }
}

找到編譯後的.class文件,執行javap -c -v -p SynchronizedTest.class命令
在這裏插入圖片描述
ACC_SYNCHRONIZED是同步方法的標誌位。
monitorentermonitorexit是同步代碼塊的進出標誌位。

synchronized底層的源碼就是引入了ObjectMonitor

重量級鎖

在jdk1.6之前synchronized被稱爲重量級鎖,是因爲底層實現的ObjectMonitor的調用過程涉及用戶態和內存態的切換,這是Linux內核的複雜運行機制決定的,大量消耗系統資源,所以效率低

優化鎖升級

流程

在這裏插入圖片描述

  • 當一個線程需要鎖資源時,首先判斷當前資源鎖持有者是否與當前線程是同一線程,如果是,拿到鎖。(偏向鎖/可重入)
  • 如果不是同一線程,通過自旋+CAS嘗試獲取鎖(自旋鎖)
  • 默認自旋10次,最終仍沒有獲得鎖,升級爲重量鎖
    在這裏插入圖片描述
    簡單說升級流程就是:無鎖->偏向鎖->輕量級鎖->重量鎖

synchronized和Lock的區別

  1. 原始構成
  • synchronized關鍵字屬於JVM層面,底層是通過monitor對象來完成,wait/notify方法也依賴於monitor對象,只有在同步塊和方法中才能調用。
  • Lock是具體類(java.util.concurrent.locks.Lock),是API層面的鎖
  1. 使用方法
  • synchronized不需要用戶手動釋放鎖,當synchronized代碼塊執行完畢後系統會自動讓線程釋放對鎖的佔用
  • ReentrantLock需要用戶手動釋放鎖,如果沒有主動釋放鎖,可能導致出現死鎖現象。需要lock() unlock()配合try-finally語句塊來完成
  1. 是否可中斷
  • synchronized不可中斷,除非拋出異常或者正常運行完成
  • ReentrantLock可中斷:
    • 設置超時方法tryLock(Long timeout,Timeunit unit)
    • lockInterruptibly()放代碼塊中,調用interrupt()可中斷
  1. 加鎖是否公平
  • synchronized是非公平鎖
  • ReentrantLock兩者都可以,默認非公平鎖,可以通過構造器傳入
  1. 鎖綁定多條件Condition
  • synchronized只有隨機喚醒或者全部喚醒
  • ReentrantLock可以通過Condition實現分組喚醒和精確喚醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditonDemo {


}

/**
 * A 打印5次 喚醒B 打印5次 再喚醒C打印5次
 */
class NumPrinter{
    private ReentrantLock lock=new ReentrantLock();
    Condition ca=lock.newCondition();
    Condition cb=lock.newCondition();
    Condition cc=lock.newCondition();

    private char name='a';

    public static void main(String[] args) {
        NumPrinter numPrinter=new NumPrinter();

        new Thread(numPrinter::printa,"A").start();
        new Thread(numPrinter::printb,"B").start();
        new Thread(numPrinter::printc,"C").start();
    }

    public void printa(){
        lock.lock();

        try {
            while (name!='a'){
                ca.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('a');
            }
            name='b';
            cb.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }public void printb(){
        lock.lock();

        try {
            while (name!='b'){
                cb.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('b');
            }
            name='c';
            cc.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }public void printc(){
        lock.lock();

        try {
            while (name!='c'){
                cc.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('c');
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

參考

https://mp.weixin.qq.com/s/2ka1cDTRyjsAGk_-ii4ngw

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