Synchronized詳解

来源:https://www.cnblogs.com/spark-cc/archive/2023/03/27/17263106.html
-Advertisement-
Play Games

sychronized是java多線程非常關鍵的一個知識點,這篇博客將從synchronized幾個用法以及代碼來學習。 sychronized的作用是能夠保證同一時間只有一個線程來運行這塊代碼,達到併發效果,如果沒有保證併發的話,在多線程編碼中就會產生致命問題,比如經典的i++,這也是資料庫併發中 ...


sychronized是java多線程非常關鍵的一個知識點,這篇博客將從synchronized幾個用法以及代碼來學習。
sychronized的作用是能夠保證同一時間只有一個線程來運行這塊代碼,達到併發效果,如果沒有保證併發的話,在多線程編碼中就會產生致命問題,比如經典的i++,這也是資料庫併發中經典的案例,i++並不是原子操作,分為三步,取數,操作,寫數,參考這段代碼,可以運行一下看下結果

public class showUnsafe1 implements Runnable{
    static int i=0;
    @Override
    public void run() {
        for(int j=0;j<10000;j++){
            i++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new showUnsafe1());
        Thread thread2 = new Thread(new showUnsafe1());
        thread1.start();  // 啟動thread1,在合適的時刻運行
        thread2.start();  // 啟動thread2,在合適的時刻運行
        thread1.join();   // 讓主線程等待thread1運行完
        thread2.join();   // 讓主線程等待thread2運行完
        System.out.println(i);
    }
}

一、synchronized四種用法

synchronized為啥這麼神奇,無它,加鎖而已,不少八股文喜歡分為兩種鎖,一種是對象鎖,一種是類鎖,還可以分為方法鎖,代碼塊鎖,靜態鎖,class鎖,我們通過代碼學習他們如何使用

1.對象鎖:方法鎖

方法鎖是用synchronized修飾的一個類方法,作用方法即是方法作用域,除了這個方法要同步,其餘不需要

public class SynchronizedObjectMethod implements Runnable{

    private static SynchronizedObjectMethod instance=new SynchronizedObjectMethod();
    public  synchronized void method(){
        System.out.println("我是對象鎖的方法修飾符形式。我叫"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"運行結束");
    }
    @Override
    public void run() {
        method();
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        while(thread1.isAlive()||thread2.isAlive()){
        }
        System.out.println("finish");
    }
}

2. 對象鎖:代碼塊形式

代碼塊鎖就是常用的同步方法塊,synchronized鎖住的是它裡面的對象,作用域就是synchonized{}裡面的代碼

public class SynchronizedObjectCodeBlock implements Runnable{
    private static SynchronizedObjectCodeBlock instance=new SynchronizedObjectCodeBlock();
    Object lock1=new Object();
    @Override
    public void run() {
        synchronized (lock1){
            System.out.println("我是對象鎖的代碼塊形式。我叫"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"運行結束");
        }

    }

    public static void main(String[] args){
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        while(thread1.isAlive()||thread2.isAlive()){
        }
        System.out.println("finish");
    }
}

3. 類鎖:class形式

class形式說的是synchronized()括弧里使用的鎖是class對象,所謂class對象指得是java文件對應的一個java.lang.class對象,所有該類生成的對象共有這個class對象 類載入機制,所以這個鎖鎖住了這個類生成的所有對象

public class SynchronizedClassClass implements Runnable{
    private static SynchronizedClassClass instance1=new SynchronizedClassClass();
    private static SynchronizedClassClass instance2=new SynchronizedClassClass();
    public void method(){
        synchronized (SynchronizedClassClass.class){
            System.out.println("我是類鎖的形式之一:修飾.class。我叫"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"運行結束");
        }
    }
    @Override
    public void run() {
        method();
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while(thread1.isAlive()||thread2.isAlive()){
        }
        System.out.println("finish");
    }
}

在這個案例中,存在兩個SynchronizedClassClass對象,但是不能同時訪問同步代碼

4.類鎖:static形式

static形式說的是static修飾synchronized修飾的方法,即static synchronized methodName,作用方法還是這個類的class對象

public class SynchronizedClassStatic implements Runnable{
    private static SynchronizedClassStatic instance1=new SynchronizedClassStatic();
    private static SynchronizedClassStatic instance2=new SynchronizedClassStatic();
    public static synchronized void method(){
        System.out.println("我是類鎖的形式之一:加static修飾。我叫"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"運行結束");
    }
    public void  method2(){
        System.out.println("我是非靜態方法,我叫"+Thread.currentThread().getName());
    }
    @Override
    public void run() {
        method();
        method2();
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while(thread1.isAlive()||thread2.isAlive()){
        }
        System.out.println("finish");
    }
}

二、synchronized的原理

讓我們從位元組碼來看看synchronized的神秘面紗, 我們反編譯synchronized修飾object的java文件

public class Decompilation {
    private Object object=new Object();

    public void insert(Thread thread){
        synchronized (object){

        }
    }
}

反編譯指令為 javap -c -v YourName.java

所以就是這個小小的Monitor Enter和monitorexit指令完成了同步操作,關於Monitor Enter和monitorexit的定義可以查看jvm文檔 中的註釋

先看看 Monitor Enter,註意紫色標註


再看看Monitor Exit

這裡就解釋清楚為什麼synchronized上的鎖是可重入的,對於更深入的理解

重點還是提到的Monitor這個概念

我們再反編譯synchronize修飾的同步方法,其結果是

與之前不一樣,這裡是在方法的訪問標識上添加了ACC_SYNCHRONIZED,它在jvm文檔中是這麼解釋

大致意思就是當調用設置了 ACC_SYNCHRONIZED 的方法時,執行線程進入監視器(monitor),然後執行這個方法,方法執行完畢後退出監視器。在執行線程擁有監視器期間,沒有其他線程可以進入這個方法.

同樣,這裡涉及了Monitor

2.1 深入Monitor

雖然HotSpot的JDK代碼沒有開元,但好在還有OpenJDK,大家有時間可以看看ObjectMonitor.hpp和ObjectMonitor.cpp,如果c語言功力不好的同學,看看註釋,大致知道每個變數啥意思即可。

image-20230327165124910

主要是_owner,_recursions,_entryList,_waitSet,此外還有header這個對象頭將對象和Monitor聯繫起來。

_owner顧名思義就是鎖的擁有者,recursions就是鎖的進入次數,初始為0,而_entryList是存放Blocked狀態的線程的,waitSet是存放Waiting狀態的線程。

在HotSpot中,一個對象是在Heap中的存儲佈局有三個部分:對象頭(Header),實例數據(Instance Data)以及對齊填充部分,而對象頭一般由兩個部分組成:MarkWord和類型指針,如果是數組對象,那麼還有數組長度信息。

而MarkWord就是連接Monitor和對象的關鍵東西。它存儲了對象運行時的一些數據,比如HashCode,GC年齡,鎖的狀態,線程所持有的鎖等。

image-20230327210942749

總之,monitor才是synchronized併發的關鍵,monitor是底層用cpp實現的一個對象,實現了鎖的狀態轉換,獲取釋放等方法,通過markword與java對象聯繫一起,而markword是嵌入在java頭部的。

三、synchornized的各種鎖以及優化

3.1 鎖的升級

從jdk1.6開始,synchronized鎖有四種狀態,級別由低到高是

無鎖,偏向鎖,輕量鎖,重量鎖

鎖的升級過程是一個很麻煩的事情,本質就是如果發生了鎖的競爭就升級鎖,直到升級到重量鎖為止,期間使用到了CAS和自旋來避免線程直接進入阻塞狀態。有興趣的同學可以看看《阿裡巴巴java性能調優實戰》這本書。

3.2 JIT 實現鎖消除和鎖粗化

鎖消除的概念比較容易理解,就是如果編譯器認定一個鎖只會被單個線程訪問,那麼這個鎖就可以被消除。而鎖粗化,簡單的說就是JIT動態編譯時發現相鄰的同步塊使用的是同一個鎖實例,那麼就合併他們,避免頻繁加鎖釋放鎖。

3.3 減少鎖的粒度

這是我們平時編程的時候,我們可以控制的,有的同學(比如我)圖省事往往可以加對象鎖的,直接加類鎖,這樣就是不地道的。

《阿裡巴巴java性能調優實戰》舉例說明減少鎖的粒度的好處,比如被拋棄的HashTable和新寵ConcurrentHashMap的轉換,就是使用了減少鎖的粒度方法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 響應式編程 1.1. 使用基於事件的範式處理非同步數據流 1.2. 和非同步編程提供了相同的性能優勢 1.3. 能夠擴展程式(特別是擴展I/O)以處理很多連接和數據源 2. 非阻塞I/O 2.1. 有效擴展伺服器的基礎 2.2. 允許伺服器用相對較少的線程處理相對較多的連接 2.2.1. 傳統的服 ...
  • 關於商業認知 2022 年復盤了過去幾年的項目經歷:很多項目商業都沒開始就死了,能商業化閉環 & 能持續一段時間的一隻手數量都沒有。 從 2022 年幾個月陸續復盤中,收穫了不少商業相關心得: 心得:一定要選合適自己方向的項目 以前我對戰略、方向的思考是極少的,很容易陷入達克效應(鄧寧-克魯格效應) ...
  • 本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 大家好,我是大彬~ 今天給大家分 ...
  • 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 配置文件中配置項 MaxDataDump 的使用方法。 ...
  • 在現在的日常開發中,不管前端還是後端,JSON 格式的數據是用得比較多的,甚至可以說無處不在。在某些業務場景下也是需要用到 JSON 的,特別是 JSON 與 Java 對象之間的轉化。 ...
  • 本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 今天來熟悉一下,關於JVM調優常 ...
  • 一.去除0的方法 BigDecimal是處理高精度的浮點數運算的常用的一個類 當需要將BigDecimal中保存的浮點數值列印出來,特別是在頁面上顯示的時候,就有可能遇到預想之外的科學技術法表示的問題。 一般直接使用 BigDecimal.toString()方法即可以完成浮點數的列印。 如: Sy ...
  • 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 配置文件中配置項 ForceIncludeModules 的使用方法。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...