synchronized 方法 導致插入數據插不進_synchronized 原理知多少

本文轉載于SegmentFault社區

作者:ytao


synchronized是 Java 編程中的一個重要的關鍵字,也是多線程編程中不可或缺的一員。本文就對它的使用和鎖的一些重要概念進行分析。

使用及原理


synchronized 是一個重量級鎖,它主要實現同步操作,在 Java 對象鎖中有三種使用方式:

  • 普通方法中使用,鎖是當前實例對象。

  • 靜態方法中使用,鎖是當前類的對象。

  • 代碼塊中使用,鎖是代碼代碼塊中配置的對象。

使用


在代碼中使用方法分別如下:

普通方法使用:

/** * 公眾號:ytao * 博客:https://ytao.top */public class SynchronizedMethodDemo{    public synchronized void demo(){        // ......    }}

靜態方法使用:

/** * 公眾號:ytao * 博客:https://ytao.top */public class SynchronizedMethodDemo{    public synchronized static void staticDemo(){        // ......    }}

代碼塊中使用:

/** * 公眾號:ytao * 博客:https://ytao.top */public class SynchronizedDemo{    public void demo(){        synchronized (SynchronizedDemo.class){            // ......        }    }}

實現原理


方法和代碼塊的實現原理使用不同方式:

代碼塊

每個對象都擁有一個monitor對象,代碼塊的{}中會插入monitorenter和monitorexit指令。當執行monitorenter指令時,會進入monitor對象獲取鎖,當執行monitorexit命令時,會退出monitor對象釋放鎖。同一時刻,只能有一個線程進入在monitorenter中。

先將SynchronizedDemo.java使用javac SynchronizedDemo.java命令將其編譯成SynchronizedDemo.class。然后使用javap -c SynchronizedDemo.class反編譯字節碼。Compiled from "SynchronizedDemo.java"public class SynchronizedDemo {  public SynchronizedDemo();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."":()V       4: return  public void demo();    Code:       0: ldc           #2                  // class SynchronizedDemo       2: dup       3: astore_1       4: monitorenter  // 進入 monitor       5: aload_1       6: monitorexit  // 退出 monitor       7: goto          15      10: astore_2      11: aload_1      12: monitorexit  // 退出 monitor      13: aload_2      14: athrow      15: return    Exception table:       from    to  target type           5     7    10   any          10    13    10   any}

上面反編碼后的代碼,有兩個monitorexit指令,一個插入在異常位置,一個插入在方法結束位置。

方法


方法中的synchronized與代碼塊中實現的方式不同,方法中會添加一個叫ACC_SYNCHRONIZED的標志,當調用方法時,首先會檢查是否有ACC_SYNCHRONIZED標志,如果存在,則獲取monitor對象,調用monitorenter和monitorexit指令。

通過javap -v -c SynchronizedMethodDemo.class命令反編譯SynchronizedMethodDemo類。-v參數即-verbose,表示輸出反編譯的附加信息。下面以反編譯普通方法為例。

Classfile /E:/SynchronizedMethodDemo.class  Last modified 2020-6-28; size 381 bytes  MD5 checksum 55ca2bbd9b6939bbd515c3ad9e59d10c  Compiled from "SynchronizedMethodDemo.java"public class SynchronizedMethodDemo  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #5.#13         // java/lang/Object."":()V   #2 = Fieldref           #14.#15        // java/lang/System.out:Ljava/io/PrintStream;   #3 = Methodref          #16.#17        // java/io/PrintStream.println:()V   #4 = Class              #18            // SynchronizedMethodDemo   #5 = Class              #19            // java/lang/Object   #6 = Utf8                  #7 = Utf8               ()V   #8 = Utf8               Code   #9 = Utf8               LineNumberTable  #10 = Utf8               demo  #11 = Utf8               SourceFile  #12 = Utf8               SynchronizedMethodDemo.java  #13 = NameAndType        #6:#7          // "":()V  #14 = Class              #20            // java/lang/System  #15 = NameAndType        #21:#22        // out:Ljava/io/PrintStream;  #16 = Class              #23            // java/io/PrintStream  #17 = NameAndType        #24:#7         // println:()V  #18 = Utf8               SynchronizedMethodDemo  #19 = Utf8               java/lang/Object  #20 = Utf8               java/lang/System  #21 = Utf8               out  #22 = Utf8               Ljava/io/PrintStream;  #23 = Utf8               java/io/PrintStream  #24 = Utf8               println{  public SynchronizedMethodDemo();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."":()V         4: return      LineNumberTable:        line 5: 0  public synchronized void demo();    descriptor: ()V    flags: ACC_PUBLIC, ACC_SYNCHRONIZED     // ACC_SYNCHRONIZED 標志    Code:      stack=1, locals=1, args_size=1         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: invokevirtual #3                  // Method java/io/PrintStream.println:()V         6: return      LineNumberTable:        line 8: 0        line 10: 6}SourceFile: "SynchronizedMethodDemo.java"

上面對代碼塊和方法的實現方式進行探究:

  • 代碼塊通過在編譯后的代碼中添加monitorenter和monitorexit指令。

  • 方法中通過添加ACC_SYNCHRONIZED標志,來決定是否調用monitor對象。

Java 對象頭


synchronized鎖的相關數據存放在 Java 對象頭中。Java 對象頭指的 HotSpot 虛擬機的對象頭,使用2個字寬或3個字寬存儲對象頭。

  • 第一部分存儲運行時的數據,hashCode、鎖標記位、是否偏向鎖、GC分代年齡等等信息,稱作為Mark Word。

  • 第二部分存儲對象類型數據的指針。

  • 第三部分,如果對象是數組的話,則用這部分來存儲數組長度。

Java 對象頭 Mark Word 存儲內容:

7d746c51b907f9d518e4b87621e90c52.png

鎖升級


synchronized 稱為重量級鎖,但 Java SE 1.6 為優化該鎖的性能而減少獲取和釋放鎖的性能消耗,引入偏向鎖和輕量級鎖。

鎖的高低級別為:無鎖→偏向鎖→輕量級鎖→重量級鎖。

其中鎖的升級是不可逆的,只能由低往高級別升,不能由高往低降。

偏向鎖


偏向鎖是優化在無多線程競爭情況下,提高程序的的運行性能而使用到的鎖。在Mark Word中存儲一個值,用來標志是否為偏向鎖,在 32 位虛擬機和 64 位虛擬機中都是使用一個字節存儲,0 為非偏向鎖,1 為是偏向鎖。

當第一次被線程獲取偏向鎖時,會將Mark Word中的偏向鎖標志設置為 1,同時使用 CAS 操作來記錄這個線程的ID。獲取到偏向鎖的線程,再次進入獲取鎖時,只需判斷Mark Word是否存儲著當前線程ID,如果是,則不需再次進行獲取鎖操作,而是直接持有該鎖。

撤銷鎖

如果有其他線程出現,嘗試獲取偏向鎖,讓偏向鎖處于競爭狀態,那么當前偏向鎖就會撤銷。

撤銷偏向鎖時,首先會暫停持有偏向鎖的線程,并將線程ID設為空,然后檢查該線程是否存活:

  • 當暫停線程非存活,則設置對象頭為無鎖狀態。

  • 當暫停線程存活,執行偏向鎖的棧,最后對象頭的保存其他獲取到偏向鎖的線程ID或者轉向無鎖狀態。

當確定代碼一定執行在多線程訪問中時,那么這時的偏向鎖是無法發揮到優勢,如果繼續使用偏向鎖就顯得過于累贅,給系統帶來不必要的性能開銷,此時可以設置 JVM 參數-XX:BiasedLocking=false來關閉偏向鎖。

輕量級鎖


代碼進入同步塊的時候,如果對象頭不是鎖定狀態,JVM 則會在當前線程的棧楨中創建一個鎖記錄的空間,將鎖對象頭的Mark Word復制一份到鎖記錄中,這份復制過來的Mark Word叫做Displaced Mark Word。然后使用 CAS 操作將鎖對象頭中的Mark Word更新為指向鎖記錄的指針。如果更新成功,當前線程則會獲得鎖,如果失敗,JVM 先檢查鎖對象的Mark Word是否指向當前線程,是指向當前線程的話,則當前線程已持有鎖,否則存在多線程競爭,當前線程會通過自旋獲取鎖,這里的自旋可以理解為循環嘗試獲取鎖,所以這過程是消耗 CPU 的過程。當輕量級鎖存在競爭狀態并自旋獲取輕量級鎖失敗時,輕量級鎖就會膨脹為重量級鎖,鎖對象的Mark Word會更新為指向重量級鎖的指針,等待獲取鎖的線程進入阻塞狀態。

解鎖

輕量級鎖解鎖是使用 CAS 操作將鎖記錄替換到Mark Word中,如果替換成功,則表示同步操作已完成。如果失敗,則表示其他競爭線程嘗試過獲取該輕量級鎖,需要在釋放鎖的同時,去喚醒其他被阻塞的線程,被喚醒的線程回去再次去競爭鎖。

總結


通過分析synchronized的使用以及 Java SE 1.6 升級優化鎖后的設計,可以看出其主要是解決是通過多加入兩級相對更輕巧的偏向鎖和輕量級鎖來優化重量級鎖的性能消耗,但是這并不是一定會起到優化作用,主要是解決大多數情況下不存在多線程競爭以及同一線程多次獲取鎖的的優化,這也是根據平時在編碼中多觀察多反思得出的權衡方案。

推薦閱讀:

《volatile 手摸手帶你解析》:
https://ytao.top/2020/03/15/18-volatile/

《Java 線程通信之 wait/notify 機制》:

https://ytao.top/2020/05/12/24-thread-wait-notify/

《Java 多線程中使用 JDK 自帶工具類實現計數器》:

https://ytao.top/2020/05/17/25-thread-count/

《Java 線程基礎,從這篇開始》:

https://ytao.top/2020/04/19/22-thread-base/

-?END -

c6111bcddfe414345c0c5aaf2f5ebc22.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/538325.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/538325.shtml
英文地址,請注明出處:http://en.pswp.cn/news/538325.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringMVC源碼解析(四)——請求處理

2019獨角獸企業重金招聘Python工程師標準>>> 前言 這一篇,將著手介紹一次請求的處理。用到了 HandlerMapping、HandlerAdapter 知識,如果遇到不是太了解,可以回顧下。 源碼分析 其實 DispatcherServlet 也只是 Servlet 的一個實現…

oracle中where中使用函數,Oracle 盡量避免在 SQL語句的WHERE子句中使用函數

-- Start在 WHERE 子句中應該盡量避免在列上使用函數,因為這樣做會使該列上的索引失效,影響SQL 語句的性能。即使該列上沒有索引,也應該避免在列上使用函數。考慮下面的情況:CREATE TABLE EMPLOYEE(NAME VARCHAR2(20) NOT NULL,--…

求近似數最值_干貨|初中數學《數的開方》知識點梳理

本章內容課標的要求● 1.了解平方根、算術平方根、立方根的概念,會用根號表示數的平方根、算術平方根、立方根。● 2.了解乘方與開方互為逆運算,會用平方運算求百以內整數的平方根,會用立方運算會求百以內整數(對應的負整數)的立方根&#xf…

第三章(續)

目錄 第二章 灰度變換與空間濾波(續)直方圖處理與函數繪圖生成直方圖直方圖均衡直方圖匹配空間濾波線性空間濾波非線性空間濾波圖像處理工具箱的標準濾波器線性空間濾波器非線性空間濾波器第二章 灰度變換與空間濾波(續) 直方圖處理與函數繪圖 生成直方圖 應用函數 imhist 語法…

Linux Mysql 安裝方法

1、檢查是否有安裝 [rootJDDB mysql]# yum list installed | grep mysql mysql-community-client.x86_64 5.6.39-2.el7 mysql56-community mysql-community-common.x86_64 5.6.39-2.el7 mysql56-community mysql-community…

oracle 經緯度算距離,根據經緯度訣別用java和Oracle存儲過程計算兩點距離

根據經緯度分別用java和Oracle存儲過程計算兩點距離create or replace procedure SP_GET_DISTANCE(cx in number,cy in number,sx in number, sy in number,distance out varchar2)isd number;x number;y number;r number;pi number;begin--開始計算r:6371229;--地球半徑pi:3.1…

Kafka集群安裝--測試--關閉

一、前提 1、kafka安裝包下載:http://kafka.apache.org/downloads 2、jdk已安裝 3、scala已安裝 4、zookeeper集群已安裝并運行二、步驟 1、對kafka_2.9.2-0.8.1.tgz進行解壓縮:tar -zxvf kafka_2.9.2-0.8.1.tgz。2、對kafka目錄進行改名:mv …

Java中的工廠模式

設計模式遵循原則 開閉原則:對擴展開放,對修改關閉里氏代換原則:只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被覆用。而衍生類也能夠在基類的基礎上增加新的行為依賴倒轉原則:開閉…

python的底層實現_Python底層封裝實現方法詳解

這篇文章主要介紹了Python底層封裝實現方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下事實上,python封裝特性的實現純屬“投機取巧”,之所以類對象無法直接調用私有方法和屬性&a…

php 附近的距離,PHP查詢附近的人及其距離的實現方法_PHP

本文實例講述了PHP查詢附近的人及其距離的實現方法。分享給大家供大家參考,具體如下:array(lat>$lat $dlat,lng>$lng-$dlng),right-top>array(lat>$lat $dlat, lng>$lng $dlng),left-bottom>array(lat>$lat - $dlat, lng>$ln…

統計指定目錄下的視頻時長

package time;import java.io.File;import org.apache.log4j.Logger;import it.sauronsoftware.jave.Encoder; import it.sauronsoftware.jave.EncoderException; import it.sauronsoftware.jave.MultimediaInfo;public class Test2 {/* 支持的后綴 */private static final Str…

怎么在cmd中運行python腳本_cmd中運行python腳本智能使用流程

(此時的ScaleMode自動變Vbuser)更有趣的是用來計算字串高、寬的TextHeight/TextWidth也變成以座標0-100的方式來表現了On Error Resume NextSet outstreemWscript.stdoutIf (LCase(Right(Wscript.fullname,11))"Wscript.exe") ThenSet objShellWscript.CreateObject(…

世界時鐘 軟件_Clocker for Mac(世界時鐘軟件)

Clocker for Mac是一款Mac平臺上免費的世界時鐘工具,方便我們查看世界各地的時間,它是開源免費的,完全沒有廣告。包括數百個時區,支持24小時制或AM / PM,macz提供Clocker mac免費版,歡迎前來下載&#xff0…

Mac 設置 NDK

2019獨角獸企業重金招聘Python工程師標準>>> 1、首先查看我自己的android studio ,找到以下路徑 如上圖,打開一個 AS 項目,file - project structure 這是我的3 個路徑 Ndk /Users/dhbm/Library/Android/sdk/ndk-bundle Sdk /User…

Workbench has not been created yet

原因是:加載的插件變更后需要清理 在啟動參數最后加入 -clean

oracle必須聲明標識符函數,引用變量時需要必須聲明標識符

SQL> declare2 pname emp.ename%type;3 psal emp.sal%type;4 begin5 select enmae,sal into pname,psal from emp where empno7782;6 dbms_output.put_line(pname||xsis||psal);7 end;8 /pname emp.ename%type;*第 2 行出現錯誤:ORA-06550: 第 2 行, 第 7 列:PLS-002…

四參數擬合曲線_每周放送|曲線擬合

曲線擬合No.1什么是曲線擬合所謂的曲線擬合,就是使用某一個模型(或者稱為方程式),將一系列的數據擬成平滑的曲線,以便觀察兩組數據之間的內在聯系,了解數據之間的變化趨勢。No.2曲線擬合的應用在數據分析時,我們有時需…

Spark集群運行jar包程序里的print日志哪里去了?

默認情況下,是輸出到stdout里的。 方法一: 進入work所在機器的spark安裝目錄下的work目錄,里面有日志輸出。 方法二: 進入spark web ui 里 點擊stdout就可以查看,如果沒有可能在其他work上。

hibernate oracle clob 注解,Hibernate3.X實現基于CLOB字段類型的注解方式:

一:Hibernate3.X實現基于CLOB字段類型的注解方式的例子:下面直接上代碼:二:UserInfo.javapackage cn.gov.csrc.cms.model;import javax.persistence.Basic;import javax.persistence.Column;import javax.persistence.Entity;impo…

Flutter下拉刷新,上拉加載更多數據

下拉刷新 很簡單,直接使用 RefreshIndicator 組件, onRefresh 為重新獲取數據的方法 Widget build(BuildContext context) {return Scaffold(body: Container(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _refresh,backgroundColo…