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

java同步器AQS架構(gòu)AbstractQueuedSynchronizer原理解析下

 更新時(shí)間:2022年03月11日 17:14:20   作者:Q.E.D  
這篇文章主要為大家介紹了java同步器AQS架構(gòu)AbstractQueuedSynchronizer原理解析下,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步

引導(dǎo)語

AQS 的內(nèi)容太多,所以我們分成了兩個(gè)章節(jié),沒有看過 AQS 上半章節(jié)的同學(xué)可以回首看一下哈,上半章節(jié)里面說了很多鎖的基本概念,基本屬性,如何獲得鎖等等,本章我們主要聊下如何釋放鎖和同步隊(duì)列兩大部分。

1、釋放鎖

釋放鎖的觸發(fā)時(shí)機(jī)就是我們常用的 Lock.unLock () 方法,目的就是讓線程釋放對資源的訪問權(quán)(流程見整體架構(gòu)圖紫色路線)。

釋放鎖也是分為兩類,一類是排它鎖的釋放,一類是共享鎖的釋放,我們分別來看下。

1.1、釋放排它鎖 release

排它鎖的釋放就比較簡單了,從隊(duì)頭開始,找它的下一個(gè)節(jié)點(diǎn),如果下一個(gè)節(jié)點(diǎn)是空的,就會(huì)從尾開始,一直找到狀態(tài)不是取消的節(jié)點(diǎn),然后釋放該節(jié)點(diǎn),源碼如下:

// unlock 的基礎(chǔ)方法
public final boolean release(int arg) {
    // tryRelease 交給實(shí)現(xiàn)類去實(shí)現(xiàn),一般就是用當(dāng)前同步器狀態(tài)減去 arg,如果返回 true 說明成功釋放鎖。
    if (tryRelease(arg)) {
        Node h = head;
        // 頭節(jié)點(diǎn)不為空,并且非初始化狀態(tài)
        if (h != null && h.waitStatus != 0)
            // 從頭開始喚醒等待鎖的節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 很有意思的方法,當(dāng)線程釋放鎖成功后,從 node 開始喚醒同步隊(duì)列中的節(jié)點(diǎn)
// 通過喚醒機(jī)制,保證線程不會(huì)一直在同步隊(duì)列中阻塞等待
private void unparkSuccessor(Node node) {
    // node 節(jié)點(diǎn)是當(dāng)前釋放鎖的節(jié)點(diǎn),也是同步隊(duì)列的頭節(jié)點(diǎn)
    int ws = node.waitStatus;
    // 如果節(jié)點(diǎn)已經(jīng)被取消了,把節(jié)點(diǎn)的狀態(tài)置為初始化
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 拿出 node 節(jié)點(diǎn)的后面一個(gè)節(jié)點(diǎn)
    Node s = node.next;
    // s 為空,表示 node 的后一個(gè)節(jié)點(diǎn)為空
    // s.waitStatus 大于0,代表 s 節(jié)點(diǎn)已經(jīng)被取消了
    // 遇到以上這兩種情況,就從隊(duì)尾開始,向前遍歷,找到第一個(gè) waitStatus 字段不是被取消的
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 這里從尾迭代,而不是從頭開始迭代是有原因的。
        // 主要是因?yàn)楣?jié)點(diǎn)被阻塞的時(shí)候,是在 acquireQueued 方法里面被阻塞的,喚醒時(shí)也一定會(huì)在 acquireQueued 方法里面被喚醒,喚醒之后的條件是,判斷當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)是否是頭節(jié)點(diǎn),這里是判斷當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn),所以這里必須使用從尾到頭的迭代順序才行,目的就是為了過濾掉無效的前置節(jié)點(diǎn),不然節(jié)點(diǎn)被喚醒時(shí),發(fā)現(xiàn)其前置節(jié)點(diǎn)還是無效節(jié)點(diǎn),就又會(huì)陷入阻塞。
        for (Node t = tail; t != null && t != node; t = t.prev)
            // t.waitStatus <= 0 說明 t 沒有被取消,肯定還在等待被喚醒
            if (t.waitStatus <= 0)
                s = t;
    }
    // 喚醒以上代碼找到的線程
    if (s != null)
        LockSupport.unpark(s.thread);
}

1.2、釋放共享鎖 releaseShared

釋放共享鎖的方法是 releaseShared,主要分成兩步:

tryReleaseShared 嘗試釋放當(dāng)前共享鎖,失敗返回 false,成功走 2;

喚醒當(dāng)前節(jié)點(diǎn)的后續(xù)阻塞節(jié)點(diǎn),這個(gè)方法我們之前看過了,線程在獲得共享鎖的時(shí)候,就會(huì)去喚醒其后面的節(jié)點(diǎn),方法名稱為:doReleaseShared。

我們一起來看下 releaseShared 的源碼:

// 共享模式下,釋放當(dāng)前線程的共享鎖
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 這個(gè)方法就是線程在獲得鎖時(shí),喚醒后續(xù)節(jié)點(diǎn)時(shí)調(diào)用的方法
        doReleaseShared();
        return true;
    }
    return false;
}

2、條件隊(duì)列的重要方法

在看條件隊(duì)列的方法之前,我們先得弄明白為什么有了同步隊(duì)列,還需要條件隊(duì)列?

主要是因?yàn)椴⒉皇撬袌鼍耙粋€(gè)同步隊(duì)列就可以搞定的,在遇到鎖 + 隊(duì)列結(jié)合的場景時(shí),就需要 Lock + Condition 配合才行,先使用 Lock 來決定哪些線程可以獲得鎖,哪些線程需要到同步隊(duì)列里面排隊(duì)阻塞;獲得鎖的多個(gè)線程在碰到隊(duì)列滿或者空的時(shí)候,可以使用 Condition 來管理這些線程,讓這些線程阻塞等待,然后在合適的時(shí)機(jī)后,被正常喚醒。

同步隊(duì)列 + 條件隊(duì)列聯(lián)手使用的場景,最多被使用到鎖 + 隊(duì)列的場景中。

所以說條件隊(duì)列也是不可或缺的一環(huán)。

接下來我們來看一下條件隊(duì)列一些比較重要的方法,以下方法都在 ConditionObject 內(nèi)部類中。

2.1、入隊(duì)列等待 await

// 線程入條件隊(duì)列
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 加入到條件隊(duì)列的隊(duì)尾
    Node node = addConditionWaiter();
    // 標(biāo)記位置 A
    // 加入條件隊(duì)列后,會(huì)釋放 lock 時(shí)申請的資源,喚醒同步隊(duì)列隊(duì)列頭的節(jié)點(diǎn)
    // 自己馬上就要阻塞了,必須馬上釋放之前 lock 的資源,不然自己不被喚醒的話,別的線程永遠(yuǎn)得不到該共享資源了
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 確認(rèn)node不在同步隊(duì)列上,再阻塞,如果 node 在同步隊(duì)列上,是不能夠上鎖的
    // 目前想到的只有兩種可能:
    // 1:node 剛被加入到條件隊(duì)列中,立馬就被其他線程 signal 轉(zhuǎn)移到同步隊(duì)列中去了
    // 2:線程之前在條件隊(duì)列中沉睡,被喚醒后加入到同步隊(duì)列中去
    while (!isOnSyncQueue(node)) {
        // this = AbstractQueuedSynchronizer$ConditionObject
        // 阻塞在條件隊(duì)列上
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 標(biāo)記位置 B
    // 其他線程通過 signal 已經(jīng)把 node 從條件隊(duì)列中轉(zhuǎn)移到同步隊(duì)列中的數(shù)據(jù)結(jié)構(gòu)中去了
    // 所以這里節(jié)點(diǎn)蘇醒了,直接嘗試 acquireQueued
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        // 如果狀態(tài)不是CONDITION,就會(huì)自動(dòng)刪除
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

await 方法有幾點(diǎn)需要特別注意:

上述代碼標(biāo)記位置 A 處,節(jié)點(diǎn)在準(zhǔn)備進(jìn)入條件隊(duì)列之前,一定會(huì)先釋放當(dāng)前持有的鎖,不然自己進(jìn)去條件隊(duì)列了,其余的線程都無法獲得鎖了;上述代碼標(biāo)記位置 B 處,此時(shí)節(jié)點(diǎn)是被 Condition.signal 或者 signalAll 方法喚醒的,此時(shí)節(jié)點(diǎn)已經(jīng)成功的被轉(zhuǎn)移到同步隊(duì)列中去了(整體架構(gòu)圖中藍(lán)色流程),所以可以直接執(zhí)行 acquireQueued 方法;Node 在條件隊(duì)列中的命名,源碼喜歡用 Waiter 來命名,所以我們在條件隊(duì)列中看到 Waiter,其實(shí)就是 Node。

await 方法中有兩個(gè)重要方法:addConditionWaiter 和 unlinkCancelledWaiters,我們一一看下。

2.1.1、addConditionWaiter

addConditionWaiter 方法主要是把節(jié)點(diǎn)放到條件隊(duì)列中,方法源碼如下:

// 增加新的 waiter 到隊(duì)列中,返回新添加的 waiter
// 如果尾節(jié)點(diǎn)狀態(tài)不是 CONDITION 狀態(tài),刪除條件隊(duì)列中所有狀態(tài)不是 CONDITION 的節(jié)點(diǎn)
// 如果隊(duì)列為空,新增節(jié)點(diǎn)作為隊(duì)列頭節(jié)點(diǎn),否則追加到尾節(jié)點(diǎn)上
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    // 如果尾部的 waiter 不是 CONDITION 狀態(tài)了,刪除
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 新建條件隊(duì)列 node
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 隊(duì)列是空的,直接放到隊(duì)列頭
    if (t == null)
        firstWaiter = node;
    // 隊(duì)列不為空,直接到隊(duì)列尾部
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

整體過程比較簡單,就是追加到隊(duì)列的尾部,其中有個(gè)重要方法叫做 unlinkCancelledWaiters,這個(gè)方法會(huì)刪除掉條件隊(duì)列中狀態(tài)不是 CONDITION 的所有節(jié)點(diǎn),我們來看下 unlinkCancelledWaiters 方法的源碼,如下:

2.1.2、unlinkCancelledWaiters

// 會(huì)檢查尾部的 waiter 是不是已經(jīng)不是CONDITION狀態(tài)了
// 如果不是,刪除這些 waiter
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    // trail 表示上一個(gè)狀態(tài),這個(gè)字段作用非常大,可以把狀態(tài)都是 CONDITION 的 node 串聯(lián)起來,即使 node 之間有其他節(jié)點(diǎn)都可以
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        // 當(dāng)前node的狀態(tài)不是CONDITION,刪除自己
        if (t.waitStatus != Node.CONDITION) {
            //刪除當(dāng)前node
            t.nextWaiter = null;
            // 如果 trail 是空的,咱們循環(huán)又是從頭開始的,說明從頭到當(dāng)前節(jié)點(diǎn)的狀態(tài)都不是 CONDITION
            // 都已經(jīng)被刪除了,所以移動(dòng)隊(duì)列頭節(jié)點(diǎn)到當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
            if (trail == null)
                firstWaiter = next;
            // 如果找到上次狀態(tài)是CONDITION的節(jié)點(diǎn)的話,先把當(dāng)前節(jié)點(diǎn)刪掉,然后把自己掛到上一個(gè)狀態(tài)是 CONDITION 的節(jié)點(diǎn)上
            else
                trail.nextWaiter = next;
            // 遍歷結(jié)束,最后一次找到的CONDITION節(jié)點(diǎn)就是尾節(jié)點(diǎn)
            if (next == null)
                lastWaiter = trail;
        }
        // 狀態(tài)是 CONDITION 的 Node
        else
            trail = t;
        // 繼續(xù)循環(huán),循環(huán)順序從頭到尾
        t = next;
    }
}

為了方便大家理解這個(gè)方法,畫了一個(gè)釋義圖,如下:

圖片描述

2.2、單個(gè)喚醒 signal 

signal 方法是喚醒的意思,比如之前隊(duì)列滿了,有了一些線程因?yàn)?take 操作而被阻塞進(jìn)條件隊(duì)列中,突然隊(duì)列中的元素被線程 A 消費(fèi)了,線程 A 就會(huì)調(diào)用 signal 方法,喚醒之前阻塞的線程,會(huì)從條件隊(duì)列的頭節(jié)點(diǎn)開始喚醒(流程見整體架構(gòu)圖中藍(lán)色部分),源碼如下:

// 喚醒阻塞在條件隊(duì)列中的節(jié)點(diǎn)
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 從頭節(jié)點(diǎn)開始喚醒
    Node first = firstWaiter;
    if (first != null)
        // doSignal 方法會(huì)把條件隊(duì)列中的節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列中去
        doSignal(first);
}
// 把條件隊(duì)列頭節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列去
private void doSignal(Node first) {
    do {
        // nextWaiter為空,說明到隊(duì)尾了
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        // 從隊(duì)列頭部開始喚醒,所以直接把頭節(jié)點(diǎn).next 置為 null,這種操作其實(shí)就是把 node 從條件隊(duì)列中移除了
        // 這里有個(gè)重要的點(diǎn)是,每次喚醒都是從隊(duì)列頭部開始喚醒,所以把 next 置為 null 沒有關(guān)系,如果喚醒是從任意節(jié)點(diǎn)開始喚醒的話,就會(huì)有問題,容易造成鏈表的割裂
        first.nextWaiter = null;
        // transferForSignal 方法會(huì)把節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列中去
        // 通過 while 保證 transferForSignal 能成功
        // 等待隊(duì)列的 node 不用管他,在 await 的時(shí)候,會(huì)自動(dòng)清除狀態(tài)不是 Condition 的節(jié)點(diǎn)(通過 unlinkCancelledWaiters 方法)
        // (first = firstWaiter) != null  = true 的話,表示還可以繼續(xù)循環(huán), = false 說明隊(duì)列中的元素已經(jīng)循環(huán)完了
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}   

我們來看下最關(guān)鍵的方法:transferForSignal。

// 返回 true 表示轉(zhuǎn)移成功, false 失敗
// 大概思路:
// 1. node 追加到同步隊(duì)列的隊(duì)尾
// 2. 將 node 的前一個(gè)節(jié)點(diǎn)狀態(tài)置為 SIGNAL,成功直接返回,失敗直接喚醒
// 可以看出來 node 的狀態(tài)此時(shí)是 0 了
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 將 node 的狀態(tài)從 CONDITION 修改成初始化,失敗返回 false
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    // 當(dāng)前隊(duì)列加入到同步隊(duì)列,返回的 p 是 node 在同步隊(duì)列中的前一個(gè)節(jié)點(diǎn)
    // 看命名是 p,實(shí)際是 pre 單詞的縮寫
    Node p = enq(node);
    int ws = p.waitStatus;
    // 狀態(tài)修改成 SIGNAL,如果成功直接返回
    // 把當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)修改成 SIGNAL 的原因,是因?yàn)?SIGNAL 本身就表示當(dāng)前節(jié)點(diǎn)后面的節(jié)點(diǎn)都是需要被喚醒的
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果 p 節(jié)點(diǎn)被取消,或者狀態(tài)不能修改成SIGNAL,直接喚醒
        LockSupport.unpark(node.thread);
    return true;
}

整個(gè)源碼下來,我們可以看到,喚醒條件隊(duì)列中的節(jié)點(diǎn),實(shí)際上就是把條件隊(duì)列中的節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列中,并把其前置節(jié)點(diǎn)狀態(tài)置為 SIGNAL。

2.3、全部喚醒 signalAll

signalAll 的作用是喚醒條件隊(duì)列中的全部節(jié)點(diǎn),源碼如下:

    public final void signalAll() {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        // 拿到頭節(jié)點(diǎn)
        Node first = firstWaiter;
        if (first != null)
            // 從頭節(jié)點(diǎn)開始喚醒條件隊(duì)列中所有的節(jié)點(diǎn)
            doSignalAll(first);
    }
    // 把條件隊(duì)列所有節(jié)點(diǎn)依次轉(zhuǎn)移到同步隊(duì)列去
    private void doSignalAll(Node first) {
        lastWaiter = firstWaiter = null;
        do {
            // 拿出條件隊(duì)列隊(duì)列頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
            Node next = first.nextWaiter;
            // 把頭節(jié)點(diǎn)從條件隊(duì)列中刪除
            first.nextWaiter = null;
            // 頭節(jié)點(diǎn)轉(zhuǎn)移到同步隊(duì)列中去
            transferForSignal(first);
            // 開始循環(huán)頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
            first = next;
        } while (first != null);
    }

從源碼中可以看出,其本質(zhì)就是 for 循環(huán)調(diào)用 transferForSignal 方法,將條件隊(duì)列中的節(jié)點(diǎn)循環(huán)轉(zhuǎn)移到同步隊(duì)列中去。

3、總結(jié)

AQS 源碼終于說完了,你都懂了么,可以在默默回憶一下 AQS 架構(gòu)圖,看看這張圖現(xiàn)在能不能看懂了。

圖片描述

以上就是java同步器AQS架構(gòu)AbstractQueuedSynchronizer原理解析下的詳細(xì)內(nèi)容,更多關(guān)于java同步器AbstractQueuedSynchronizer的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java每隔兩個(gè)數(shù)刪掉一個(gè)數(shù)問題詳解

    Java每隔兩個(gè)數(shù)刪掉一個(gè)數(shù)問題詳解

    這篇文章主要介紹了Java每隔兩個(gè)數(shù)刪掉一個(gè)數(shù)問題詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Java的特點(diǎn)和優(yōu)點(diǎn)(動(dòng)力節(jié)點(diǎn)整理)

    Java的特點(diǎn)和優(yōu)點(diǎn)(動(dòng)力節(jié)點(diǎn)整理)

    由于Java語言的設(shè)計(jì)者們十分熟悉C++語言,所以在設(shè)計(jì)時(shí)很好地借鑒了C++語言。可以說,Java語言是一種比C++語言“還面向?qū)ο蟆钡囊环N編程語言,下面通過本文說下java的特點(diǎn)和優(yōu)點(diǎn)
    2017-03-03
  • 詳解Java變量與常量

    詳解Java變量與常量

    這篇文章主要介紹了Java變量與常量,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Spring MVC之WebApplicationContext_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Spring MVC之WebApplicationContext_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要介紹了Spring MVC之WebApplicationContext的相關(guān)資料,需要的朋友可以參考下
    2017-08-08
  • SpringBoot整合RabbitMQ實(shí)現(xiàn)消息確認(rèn)機(jī)制

    SpringBoot整合RabbitMQ實(shí)現(xiàn)消息確認(rèn)機(jī)制

    這篇文章主要介紹了SpringBoot整合RabbitMQ實(shí)現(xiàn)消息確認(rèn)機(jī)制,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • Java Synchronize下的volatile關(guān)鍵字詳解

    Java Synchronize下的volatile關(guān)鍵字詳解

    這篇文章主要介紹了Java Synchronize下的volatile關(guān)鍵字詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 詳解Java代碼常見優(yōu)化方案

    詳解Java代碼常見優(yōu)化方案

    這篇文章主要介紹了Java代碼常見優(yōu)化方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java圖像處理教程之正片疊底效果的實(shí)現(xiàn)

    Java圖像處理教程之正片疊底效果的實(shí)現(xiàn)

    正片疊底效果是我們平時(shí)在Photoshop中會(huì)見到的一種效果,下面這篇文章主要給大家介紹了關(guān)于利用Java如何實(shí)現(xiàn)正片疊底的效果,分享出來供大家參考學(xué)習(xí),文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友可以參考借鑒,下面來一起看看詳細(xì)的介紹吧。
    2017-09-09
  • 誤將.idea文件提交至git后刪除的操作方法

    誤將.idea文件提交至git后刪除的操作方法

    這篇文章主要介紹了誤將.idea文件提交至git后刪除的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • 新手入門學(xué)習(xí)Spring Freemarker教程解析

    新手入門學(xué)習(xí)Spring Freemarker教程解析

    這篇文章主要介紹了新手入門學(xué)習(xí)Freemarker教程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10

最新評論