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
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...