欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程中synchronized的用法與原理解析

 更新時間:2024年01月29日 10:36:43   作者:Smallc0de  
這篇文章主要介紹了Java線程中synchronized的用法與原理解析,只要有線程,就會有并發(fā)的現(xiàn)象,也同時會產(chǎn)生數(shù)據(jù)不一致,那么對于需要使用同一個數(shù)據(jù)的兩個線程,就會產(chǎn)生沖突,那么就引出了鎖的概念,本篇會針對性的說下synchronized這個關(guān)鍵字,需要的朋友可以參考下

前言

說到Java就必然會考慮到線程的問題,無論工作中學(xué)習(xí)中有沒有直接接觸過多線程開發(fā),手寫過線程調(diào)用,在這個底層已經(jīng)到了多核多緩存的硬件時代,多線程是任何碼農(nóng)都繞不過的一個事情。只要有線程,就會有并發(fā)的現(xiàn)象,也同時會產(chǎn)生數(shù)據(jù)不一致。那么對于需要使用同一個數(shù)據(jù)的兩個線程,就會產(chǎn)生沖突,那么就引出了鎖的概念。鎖有很多種本篇會針對性的說下synchronized這個關(guān)鍵字是如何保證線程的有序進(jìn)行。

現(xiàn)實(shí)場景

能用到多線程的場景有很多,比如餐館叫號,醫(yī)院掛號,銀行辦理業(yè)務(wù)等等數(shù)不勝數(shù)。最近春運(yùn)要到了,就以買火車票為例子?,F(xiàn)在有5個人買北京站買車票,一共有三個窗口,于是5個人去車站買票。車票么一張票對應(yīng)一個座兒,誰買了誰坐,這個大家都明白。但是如果沒有任何的限制,大概會有下面幾個可能:

  • 重號:有三個人同時去買北京到上海的票,于是三個售票員同時發(fā)現(xiàn)系統(tǒng)顯示某車廂里1號座位可以賣,于是三個售票員同時給了三個人這個車廂的1號座票,這三張一樣票誰用就成了麻煩,這就是重號。
  • 錯號:有兩個人同時去買票,售票員應(yīng)該賣出北京到上海給乘客1,應(yīng)該賣出北京到成都給乘客2,于是按照這樣的{北京上海、北京成都}的順序提交了訂單到系統(tǒng)。但是付錢的時候乘客2單身久了手比較快先付了錢,結(jié)果把{北京上海}的票給買到了,沒辦法去成都了,這個就是系統(tǒng)發(fā)生了錯號。
  • 跳號:跳號和錯號發(fā)生的場景差不多。網(wǎng)點(diǎn)升級現(xiàn)在有50個窗口,一共有兩百張票可以賣對應(yīng)訂單號1-200,但是有400位乘客同時去買。結(jié)果訂單號生成的順序亂七八糟,而且超過200的訂單號都有可能,這種情況發(fā)生就叫做跳號。

這種問題在日常生活中是無法容忍的,因此必須使得某些變量在整個系統(tǒng)中是唯一,并且是線程共享的,因此java中就有了關(guān)鍵字static。但是static雖然可以保證唯一性,但是無法解決上面的三個問題,尤其是并發(fā)量比較大的時候。

問題分析

這種問題是如何發(fā)生的呢?簡單來說就是前一個線程拿到數(shù)據(jù)做了修改,但是還沒有輸出就被第二個線程拿取用了。

比如下圖,本來應(yīng)該輸出101的Thread1,經(jīng)過Thread2的競爭輸出了102,這就是為什么產(chǎn)生了重號,跳號,錯號等等這些問題的原因。

總結(jié)來說多線程會導(dǎo)致數(shù)據(jù)不一致問題。

在這里插入圖片描述

解決問題

導(dǎo)致這個問題的根本原因在哪呢?就是number++這個操作,這個操作并不是一個原子操作。

它可以被拆分為三步:

1.讀取number;

2.number+1;

3.回寫主存。

這些步驟都走完了才會輪到輸出。所以number++和輸出應(yīng)該是一個業(yè)務(wù)邏輯內(nèi)的事情,也就是說在邏輯上應(yīng)該是具有原子性的,不可分割的。

如何對這一塊邏輯進(jìn)行封鎖呢?Java里可以使用synchronized關(guān)鍵字,當(dāng)然也可以使用lock,但是本篇的主角不是lock。

//比如我們可以把當(dāng)前對象鎖起來。
synchronized (this){
    while(number<100){
        System.out.println("本次的號碼是:"+number++);
    }
}

這樣鎖起來以后,在執(zhí)行while循環(huán)的時候,就不會在允許其他線程去干擾這部分執(zhí)行,要么執(zhí)行完,要么都不執(zhí)行。當(dāng)整個while的內(nèi)容有了不可分割的屬性以后,它就具有了原子性。

根據(jù)這個邏輯,當(dāng)Thread1占有資源時,Thread2只能等待Thread1中synchronized塊里的內(nèi)容運(yùn)行完畢以后,才可以獲取資源,以此類推。

在這里插入圖片描述

synchronized 的鎖機(jī)制

上面的解決辦法,就是利用了synchronized鎖機(jī)制去實(shí)現(xiàn)數(shù)據(jù)同步的,從而保證了數(shù)據(jù)的一致性。從上面的例子來看,鎖機(jī)制大概有兩種特性:

  • 排他性:也叫做互斥性、獨(dú)占性,即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實(shí)現(xiàn)多線程中的協(xié)調(diào)機(jī)制,這樣在同一時間只有一個線程對需同步的代碼塊(復(fù)合操作)進(jìn)行訪問。排他性我們也往往稱為操作的原子性。
  • 可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對于隨后獲得該鎖的另一個線程是可見的(即在獲得鎖時應(yīng)獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續(xù)操作從而引起不一致。比如:t1修改了number為101,t2要知道number被修改了,才能對number操作繼續(xù)修改為102。

synchronized 的用法

synchronized 的用法很簡單,大概分為兩種。可以根據(jù)修飾對象分類,也可以根據(jù)獲取的鎖分類。

根據(jù)修飾對象分類:

synchronized關(guān)鍵字修飾在方法上

*****synchronized 可以加在非靜態(tài)方法上*****
public synchronized void methodName(){
    // code ......
}
*****synchronized 可以加在靜態(tài)方法上*****
public synchronized static void methodName(){
    // code ......
}

synchronized關(guān)鍵字修飾代碼塊

*****synchronized 可以加在某個對象上*****
public void methodName3(){
    synchronized (this){ //這里可以寫任意對象,此時是當(dāng)前對象
        // code ......
    }
}
//也可以是任意對象。但是這個對象必須是final的,因?yàn)椴煌木€程會創(chuàng)建不同的對象,也就會鎖住不同的變量,因此這里就會出現(xiàn)不一致問題。
private final Object object =new Object();
public void methodName4(){
    synchronized (object){
        // code ......
    }
}
*****synchronized 可以加在某個類上*****
public void methodName5(){
    synchronized (SynTest.class){
        // code ......
    }
}

根據(jù)獲取的鎖分類:

獲取對象鎖

//就是再說下面兩種修飾方法:
synchronized(this|object) {}  //加在對象上
synchronized 修飾非靜態(tài)方法

在 Java 中,每個對象都會有一個 monitor 對象,這個對象其實(shí)就是 Java 對象的鎖,通常會被稱為“內(nèi)置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨(dú)立的對象鎖,互不干擾。

但是一旦引用類型加上final就變味了,final修飾引用類型后,在對其初始化之后便不能再讓其指向其他對象了,但該引用所指向的對象的內(nèi)容是可以發(fā)生變化的。這也是為什么上面說對象必須是final的。

獲取類鎖

//就是再說下面兩種修飾方法:
synchronized(ClassName.class) {} //加在類上
synchronized 修飾靜態(tài)方法

在 Java 中,針對每個類也有一個鎖,可以稱為“類鎖”,類鎖實(shí)際上是通過對象鎖實(shí)現(xiàn)的,即類的 Class 對象鎖。當(dāng)ClassLoader把class文件加載到方法區(qū)的時候,會在堆區(qū)生產(chǎn)一個Class對象,每個類只有一個 Class 對象,因此某個類的所有對象會共享同一個Class對象。正是由于使用的是同一個Class對象,該類所有對象都會被synchronized所限制,所以每個類只有一個類鎖。

synchronized 代碼塊堆棧分析

為了明白synchronized的運(yùn)行原理,首先先看下synchronized關(guān)鍵字在運(yùn)行時的表現(xiàn),有這么一段小程序作為測試程序。

public class SyncTest {
    public void method(){
        synchronized(this){
            try {
                TimeUnit.MINUTES.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" has started");
        }
    }
    public static void main(String[] args) {
        SyncTest test=new SyncTest();
        for (int i = 0; i <5 ; i++) {
            new Thread(test::method).start();
        }
    }
}

使用cmd調(diào)出控制臺,鍵入jps命令顯示當(dāng)前運(yùn)行的java線程。

在這里插入圖片描述

找到正在運(yùn)行的SyncTest線程id:25784,然后鍵入jstack 25784,查看線程運(yùn)行狀況。可以看到Thread-1到Thread-4都被阻塞了,只有Thread-0 處于time wait狀態(tài),那么就是說明synchronized確實(shí)做到了排他性,一旦一個線程占用了某資源,其他線程執(zhí)行等待資源。也正是這種狀態(tài),最終我們能夠?qū)崿F(xiàn)同步。

在這里插入圖片描述

synchronized 代碼塊實(shí)現(xiàn)原理

synchronized到底底層是怎么實(shí)現(xiàn)的呢?接下來就必須通過jvm的反編譯指令進(jìn)行一個分析,使用方法在最后附錄里。

所以我們找到SyncTest.class文件,使用javap –v SyncTest命令就可以把這個class文件的附加信息都拿出來。

那么我們從解析的反編譯文件里找到method()方法:

public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter		***** monitorenter互斥的入口
         4: getstatic     #2                  // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit;
         7: ldc2_w        #3                  // long 10l
        10: invokevirtual #5                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
        13: goto          21
        16: astore_2
        17: aload_2
        18: invokevirtual #7                  // Method java/lang/InterruptedException.printStackTrace:()V
        21: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        24: new           #9                  // class java/lang/StringBuilder
        27: dup
        28: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
        31: invokestatic  #11                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        34: invokevirtual #12                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        37: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        40: ldc           #14                 // String  has started
        42: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        45: invokevirtual #15                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        48: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        51: aload_1
        52: monitorexit		***** 一直到這里退出,可以算作互斥的出口
        53: goto          61
        56: astore_3
        57: aload_1
        58: monitorexit		***** 發(fā)現(xiàn)還有一個monitorexit,這個是異常出口,下面有解釋
        59: aload_3
        60: athrow
        61: return

從反編譯的文件結(jié)合上述我們已經(jīng)說過的鎖機(jī)制,很顯然匯編代碼中monitorenter就是互斥的入口,然后直到下面第52行執(zhí)行monitorexit退出。在此期間執(zhí)行的命令其他線程就無法操作了,這就是所謂的鎖(Lock),所以鎖(Lock)鎖住的是什么呢?鎖住的就是從第3行到第52行之間的內(nèi)容,那么整個從monitorenter開始到monitorexit退出就是我們常說的鎖的原理。

這也解釋了為什么synchronized塊要加在一個對象上或者需要加在一個class上,因?yàn)閟ynchronized關(guān)鍵字需要和class文件也就是對象或者類相關(guān)聯(lián),synchronized塊就是提供這樣一個monitorenter標(biāo)記給相應(yīng)的class文件,因此需要加上一個對象或者類作為標(biāo)記。

除此以外還有一個小要點(diǎn):繼續(xù)往下走,發(fā)現(xiàn)第58行還有一個monitorexit標(biāo)記,為什么一個入口要有兩個出口呢?

因?yàn)榈谝粋€出口是正常出口,程序執(zhí)行無誤就從第52行正常退出;如果程序執(zhí)行發(fā)生異常,無法執(zhí)行到第52行的出口,就從第58行的出口退出。

synchronized 作為方法關(guān)鍵字

上面說完synchronized塊是如何做到的鎖,下面我們看看synchronized作為方法關(guān)鍵字的時候會不會還是一樣的,首先添加下面這樣一個方法到測試程序中:

public static synchronized void methodName(){
    while(number<100){
        System.out.println("本次的號碼是:"+number++);
    }
}

然后如法炮制,運(yùn)行javap –v SyncTest命令。

 public static synchronized void methodName();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED		*方法標(biāo)記
    Code:
      stack=5, locals=0, args_size=0
         0: getstatic     #15                 // Field number:I
         3: bipush        100
         5: if_icmpge     44
         8: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: new           #7                  // class java/lang/StringBuilder
        14: dup
        15: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
        18: ldc           #16                 // String 本次的號碼是:
        20: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        23: getstatic     #15                 // Field number:I
        26: dup
        27: iconst_1
        28: iadd
        29: putstatic     #15                 // Field number:I
        32: invokevirtual #17                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        35: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        38: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        41: goto          0
        44: return
      LineNumberTable:
        line 19: 0
        line 20: 8
        line 22: 44
      StackMapTable: number_of_entries = 2
        frame_type = 0 /* same */
        frame_type = 43 /* same */

發(fā)現(xiàn)這次執(zhí)行后,從上到下整個方法都沒有monitorenter這個作為入口的關(guān)鍵字。雖然沒有找到但是我們可以在方法最上面找到這樣一行標(biāo)記:flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED。仔細(xì)觀察這行標(biāo)記,其實(shí)就是我們寫在方法前面的修飾詞:public、 static和synchronized。其實(shí)ACC_SYNCHRONIZED就是互斥開始標(biāo)記,一旦發(fā)現(xiàn)這個標(biāo)記就表示這個方法是一個同步方法,或者叫互斥方法。當(dāng)一個線程執(zhí)行含有ACC_SYNCHRONIZED標(biāo)記的方法的時候,別的線程就無法調(diào)用這個方法。這個就是synchronized關(guān)鍵字對方法加鎖的原理。

總結(jié)

到此synchronized的基本原理和使用方法就告一段落了。本篇通過實(shí)例講解了synchronized的場景和synchronized的使用方法,通過反編譯class字節(jié)碼文件以后的輸出,分析出synchronized方法是用的ACC_SYNCHRONIZED信號作為互斥標(biāo)記,而synchronized塊則是用的monitorenter和monitorexit作為互斥標(biāo)記,兩種不同的機(jī)制去實(shí)現(xiàn)同步。但是某一塊代碼一旦被加上synchronized關(guān)鍵字以后,相當(dāng)于對其他線程鎖住了,因此synchronized算是一個相當(dāng)重量級的鎖。因此使用synchronized需要注意一些問題:

  1. 與moniter關(guān)聯(lián)的對象不能為空,也就是說synchronized參數(shù)不能為null。
  2. synchronized作用域太大,導(dǎo)致程序里有大量不必要的單線程執(zhí)行過程,而不是多線程執(zhí)行。
  3. 不同的monitor企圖鎖相同的方法,結(jié)果多個鎖的交叉導(dǎo)致出現(xiàn):A線程等B線程釋放資源,B線程等A線程釋放資源的死鎖問題

正是由于有這些問題,為了提高synchronized可用性,Java官方針對synchronized關(guān)鍵字也進(jìn)行了優(yōu)化

附:如何找到并使用Java反編譯文件

首先進(jìn)入項(xiàng)目路徑的/out路徑,找到.class文件:

在這里插入圖片描述

然后呼叫出控制臺,進(jìn)入當(dāng)前目錄:

在這里插入圖片描述

輸入命令javap –v [字節(jié)碼名稱]打印額外信息。

在這里插入圖片描述

javap –v 命令說明:

在這里插入圖片描述

到此這篇關(guān)于Java線程中synchronized的用法與原理解析的文章就介紹到這了,更多相關(guān)synchronized用法與原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring的DI依賴注入詳解

    Spring的DI依賴注入詳解

    這篇文章主要為大家介紹了Spring的DI依賴注入,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • Mybatis批處理、Mysql深分頁操作

    Mybatis批處理、Mysql深分頁操作

    這篇文章主要介紹了Mybatis批處理、Mysql深分頁操作,Mybatis批量操作包括Foreach方式和ExecutorType.BATCH插入操作,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • Java給實(shí)體每一個字段賦默認(rèn)值詳細(xì)代碼示例

    Java給實(shí)體每一個字段賦默認(rèn)值詳細(xì)代碼示例

    這篇文章主要給大家介紹了關(guān)于Java給實(shí)體每一個字段賦默認(rèn)值的相關(guān)資料,在編程過程中有時會出現(xiàn)這樣一種情況,在查詢無結(jié)果時我們需要給實(shí)體賦默認(rèn)值,需要的朋友可以參考下
    2023-09-09
  • Java進(jìn)階之高并發(fā)核心Selector詳解

    Java進(jìn)階之高并發(fā)核心Selector詳解

    前幾篇文章介紹了Java高并發(fā)的一些基礎(chǔ)內(nèi)容,認(rèn)識了Channel,Buffer和Selector的基本用法,有了感性認(rèn)識之后,來看看Selector的底層是如何實(shí)現(xiàn)的。,需要的朋友可以參考下
    2021-05-05
  • 基于Java編寫簡單的Excel工具類

    基于Java編寫簡單的Excel工具類

    這篇文章主要為大家詳細(xì)介紹了如何基于Java編寫簡單的Excel工具類,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,有需要的小伙伴可以參考下
    2024-02-02
  • Jenkins+Docker+Gitee+SpringBoot自動化部署

    Jenkins+Docker+Gitee+SpringBoot自動化部署

    本文主要介紹了Jenkins+Docker+Gitee+SpringBoot自動化部署,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下


    2022-03-03
  • SpringBoot @ComponentScan掃描的局限性方式

    SpringBoot @ComponentScan掃描的局限性方式

    文章總結(jié):SpringBoot的@ComponentScan注解在掃描組件時存在局限性,只能掃描指定的包及其子包,無法掃描@SpringBootApplication注解自動配置的組件,使用@SpringBootApplication注解可以解決這一問題,它集成了@Configuration、@EnableAutoConfiguration
    2025-01-01
  • springboot 使用yml配置文件給靜態(tài)變量賦值教程

    springboot 使用yml配置文件給靜態(tài)變量賦值教程

    這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • 基于Javamail實(shí)現(xiàn)發(fā)送郵件(QQ/網(wǎng)易郵件服務(wù)器)

    基于Javamail實(shí)現(xiàn)發(fā)送郵件(QQ/網(wǎng)易郵件服務(wù)器)

    這篇文章主要介紹了基于Javamail實(shí)現(xiàn)發(fā)送郵件,分別使用QQ郵箱作為smtp郵件服務(wù)器發(fā)送郵件,使用網(wǎng)易郵箱作為smtp郵件服務(wù)器發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • JWT原理與java操作jwt驗(yàn)證詳解

    JWT原理與java操作jwt驗(yàn)證詳解

    這篇文章主要介紹了JWT原理與java操作jwt驗(yàn)證,詳細(xì)分析了JWT的基本概念、原理與java基于JWT進(jìn)行token驗(yàn)證的相關(guān)操作技巧,需要的朋友可以參考下
    2023-06-06

最新評論