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

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

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

前言

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

現(xiàn)實(shí)場(chǎng)景

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

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

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

問題分析

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

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

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

在這里插入圖片描述

解決問題

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

它可以被拆分為三步:

1.讀取number;

2.number+1;

3.回寫主存。

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

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

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

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

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

在這里插入圖片描述

synchronized 的鎖機(jī)制

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

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

synchronized 的用法

synchronized 的用法很簡(jiǎn)單,大概分為兩種??梢愿鶕?jù)修飾對(duì)象分類,也可以根據(jù)獲取的鎖分類。

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

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

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

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

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

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

獲取對(duì)象鎖

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

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

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

獲取類鎖

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

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

synchronized 代碼塊堆棧分析

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

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)出控制臺(tái),鍵入jps命令顯示當(dāng)前運(yùn)行的java線程。

在這里插入圖片描述

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

在這里插入圖片描述

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

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

所以我們找到SyncTest.class文件,使用javap –v SyncTest命令就可以把這個(gè)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)還有一個(gè)monitorexit,這個(gè)是異常出口,下面有解釋
        59: aload_3
        60: athrow
        61: return

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

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

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

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

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

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

public static synchronized void methodName(){
    while(number<100){
        System.out.println("本次的號(hào)碼是:"+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 本次的號(hào)碼是:
        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í)行后,從上到下整個(gè)方法都沒有monitorenter這個(gè)作為入口的關(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)這個(gè)標(biāo)記就表示這個(gè)方法是一個(gè)同步方法,或者叫互斥方法。當(dāng)一個(gè)線程執(zhí)行含有ACC_SYNCHRONIZED標(biāo)記的方法的時(shí)候,別的線程就無法調(diào)用這個(gè)方法。這個(gè)就是synchronized關(guān)鍵字對(duì)方法加鎖的原理。

總結(jié)

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

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

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

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

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

在這里插入圖片描述

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

在這里插入圖片描述

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

在這里插入圖片描述

javap –v 命令說明:

在這里插入圖片描述

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

相關(guān)文章

  • Spring的DI依賴注入詳解

    Spring的DI依賴注入詳解

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

    Mybatis批處理、Mysql深分頁操作

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

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

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

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

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

    基于Java編寫簡(jiǎn)單的Excel工具類

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

    Jenkins+Docker+Gitee+SpringBoot自動(dòng)化部署

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


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

    SpringBoot @ComponentScan掃描的局限性方式

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

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

    這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    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ì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    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

最新評(píng)論