日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

synchronized 方法 导致插入数据插不进_synchronized 原理知多少

發(fā)布時(shí)間:2025/3/11 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 synchronized 方法 导致插入数据插不进_synchronized 原理知多少 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文轉(zhuǎn)載于SegmentFault社區(qū)

作者:ytao


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

使用及原理


synchronized 是一個(gè)重量級(jí)鎖,它主要實(shí)現(xiàn)同步操作,在 Java 對(duì)象鎖中有三種使用方式:

  • 普通方法中使用,鎖是當(dāng)前實(shí)例對(duì)象。

  • 靜態(tài)方法中使用,鎖是當(dāng)前類(lèi)的對(duì)象。

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

使用


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

普通方法使用:

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

靜態(tài)方法使用:

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

代碼塊中使用:

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

實(shí)現(xiàn)原理


方法和代碼塊的實(shí)現(xiàn)原理使用不同方式:

代碼塊

每個(gè)對(duì)象都擁有一個(gè)monitor對(duì)象,代碼塊的{}中會(huì)插入monitorenter和monitorexit指令。當(dāng)執(zhí)行monitorenter指令時(shí),會(huì)進(jìn)入monitor對(duì)象獲取鎖,當(dāng)執(zhí)行monitorexit命令時(shí),會(huì)退出monitor對(duì)象釋放鎖。同一時(shí)刻,只能有一個(gè)線程進(jìn)入在monitorenter中。

先將SynchronizedDemo.java使用javac SynchronizedDemo.java命令將其編譯成SynchronizedDemo.class。然后使用javap -c SynchronizedDemo.class反編譯字節(jié)碼。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 // 進(jìn)入 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}

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

方法


方法中的synchronized與代碼塊中實(shí)現(xiàn)的方式不同,方法中會(huì)添加一個(gè)叫ACC_SYNCHRONIZED的標(biāo)志,當(dāng)調(diào)用方法時(shí),首先會(huì)檢查是否有ACC_SYNCHRONIZED標(biāo)志,如果存在,則獲取monitor對(duì)象,調(diào)用monitorenter和monitorexit指令。

通過(guò)javap -v -c SynchronizedMethodDemo.class命令反編譯SynchronizedMethodDemo類(lèi)。-v參數(shù)即-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 標(biāo)志 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"

上面對(duì)代碼塊和方法的實(shí)現(xiàn)方式進(jìn)行探究:

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

  • 方法中通過(guò)添加ACC_SYNCHRONIZED標(biāo)志,來(lái)決定是否調(diào)用monitor對(duì)象。

Java 對(duì)象頭


synchronized鎖的相關(guān)數(shù)據(jù)存放在 Java 對(duì)象頭中。Java 對(duì)象頭指的 HotSpot 虛擬機(jī)的對(duì)象頭,使用2個(gè)字寬或3個(gè)字寬存儲(chǔ)對(duì)象頭。

  • 第一部分存儲(chǔ)運(yùn)行時(shí)的數(shù)據(jù),hashCode、鎖標(biāo)記位、是否偏向鎖、GC分代年齡等等信息,稱作為Mark Word。

  • 第二部分存儲(chǔ)對(duì)象類(lèi)型數(shù)據(jù)的指針。

  • 第三部分,如果對(duì)象是數(shù)組的話,則用這部分來(lái)存儲(chǔ)數(shù)組長(zhǎng)度。

Java 對(duì)象頭 Mark Word 存儲(chǔ)內(nèi)容:

鎖升級(jí)


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

鎖的高低級(jí)別為:無(wú)鎖→偏向鎖→輕量級(jí)鎖→重量級(jí)鎖。

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

偏向鎖


偏向鎖是優(yōu)化在無(wú)多線程競(jìng)爭(zhēng)情況下,提高程序的的運(yùn)行性能而使用到的鎖。在Mark Word中存儲(chǔ)一個(gè)值,用來(lái)標(biāo)志是否為偏向鎖,在 32 位虛擬機(jī)和 64 位虛擬機(jī)中都是使用一個(gè)字節(jié)存儲(chǔ),0 為非偏向鎖,1 為是偏向鎖。

當(dāng)?shù)谝淮伪痪€程獲取偏向鎖時(shí),會(huì)將Mark Word中的偏向鎖標(biāo)志設(shè)置為 1,同時(shí)使用 CAS 操作來(lái)記錄這個(gè)線程的ID。獲取到偏向鎖的線程,再次進(jìn)入獲取鎖時(shí),只需判斷Mark Word是否存儲(chǔ)著當(dāng)前線程ID,如果是,則不需再次進(jìn)行獲取鎖操作,而是直接持有該鎖。

撤銷(xiāo)鎖

如果有其他線程出現(xiàn),嘗試獲取偏向鎖,讓偏向鎖處于競(jìng)爭(zhēng)狀態(tài),那么當(dāng)前偏向鎖就會(huì)撤銷(xiāo)。

撤銷(xiāo)偏向鎖時(shí),首先會(huì)暫停持有偏向鎖的線程,并將線程ID設(shè)為空,然后檢查該線程是否存活:

  • 當(dāng)暫停線程非存活,則設(shè)置對(duì)象頭為無(wú)鎖狀態(tài)。

  • 當(dāng)暫停線程存活,執(zhí)行偏向鎖的棧,最后對(duì)象頭的保存其他獲取到偏向鎖的線程ID或者轉(zhuǎn)向無(wú)鎖狀態(tài)。

當(dāng)確定代碼一定執(zhí)行在多線程訪問(wèn)中時(shí),那么這時(shí)的偏向鎖是無(wú)法發(fā)揮到優(yōu)勢(shì),如果繼續(xù)使用偏向鎖就顯得過(guò)于累贅,給系統(tǒng)帶來(lái)不必要的性能開(kāi)銷(xiāo),此時(shí)可以設(shè)置 JVM 參數(shù)-XX:BiasedLocking=false來(lái)關(guān)閉偏向鎖。

輕量級(jí)鎖


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

解鎖

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

總結(jié)


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

推薦閱讀:

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

《Java 線程通信之 wait/notify 機(jī)制》:

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

《Java 多線程中使用 JDK 自帶工具類(lèi)實(shí)現(xiàn)計(jì)數(shù)器》:

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

《Java 線程基礎(chǔ),從這篇開(kāi)始》:

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

-?END -

總結(jié)

以上是生活随笔為你收集整理的synchronized 方法 导致插入数据插不进_synchronized 原理知多少的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。