摘要:前言上一篇文章我們講了的同步代碼塊這一篇我們來(lái)看看同步代碼塊之間的協(xié)作與通信閱讀本篇前你需要知道什么是同步代碼塊什么是監(jiān)視器鎖還不是很了解的同學(xué)建議先去看一看上一篇文章本文的源碼基于系列文章目錄概述在中我們可以使用這個(gè)方法來(lái)實(shí)現(xiàn)同步代碼塊之
前言
上一篇文章我們講了java的同步代碼塊, 這一篇我們來(lái)看看同步代碼塊之間的協(xié)作與通信.
閱讀本篇前你需要知道什么是同步代碼塊, 什么是監(jiān)視器鎖, 還不是很了解的同學(xué)建議先去看一看上一篇文章.
本文的源碼基于JDK1.8
系列文章目錄
概述在Java中, 我們可以使用
wait()
wait(long timeout)
wait(long timeout, int nanos)
notify()
notifyAll()
這5個(gè)方法來(lái)實(shí)現(xiàn)同步代碼塊之間的通信, 注意, 我說(shuō)的是同步代碼塊之間的通信, 這意味著:
調(diào)用該方法的當(dāng)前線程必須持有對(duì)象的監(jiān)視器鎖
(源碼注釋: The current thread must own this object"s monitor.)
其實(shí), 這句話換個(gè)通俗點(diǎn)的說(shuō)法就是: 只能在同步代碼塊中使用這些方法.
道理很簡(jiǎn)單, 因?yàn)橹挥羞M(jìn)入了同步代塊, 才能獲得監(jiān)視器鎖.
wait方法的作用是, 阻塞當(dāng)前線程(阻塞的原因常常是一些必要的條件還沒(méi)有滿足), 讓出監(jiān)視器鎖, 不再參與鎖競(jìng)爭(zhēng), 直到其他線程來(lái)通知(告知必要的條件已經(jīng)滿足了), 或者直到設(shè)定的超時(shí)等待時(shí)間到了.
notify和notifyAll方法的作用是, 通知那些調(diào)用了wait方法的線程, 讓它們從wait處返回.
可見(jiàn), wait 和 notify 方法一般是成對(duì)使用的, 我把它簡(jiǎn)單的總結(jié)為:
等通知
wait 是等, notify 是通知.
為了給大家一個(gè)感性的認(rèn)識(shí), 我這里打個(gè)比方:
假設(shè)你和舍友一起租了個(gè)兩室一廳一廚一衛(wèi)的房子, 天這么熱, 當(dāng)然每天都要洗澡啦, 但是衛(wèi)生間只有一個(gè), 同一時(shí)間, 只有一個(gè)人能用.
這時(shí)候, 你先下班回來(lái)了, 準(zhǔn)備要洗澡, 剛進(jìn)浴室, 突然想起來(lái)你的專用防脫洗發(fā)膏用完了, 查了下快遞說(shuō)是1小時(shí)后才能送到, 但這時(shí)候你的舍友回來(lái)了, 他也要洗澡, 所以你總不能"站著茅坑不拉屎"吧, 所以你主動(dòng)讓出了浴室(調(diào)用wait方法, 讓出監(jiān)視器鎖), 讓舍友先洗, 自己等快遞.
過(guò)了一個(gè)小時(shí), 快遞送來(lái)了你的防脫洗發(fā)膏(調(diào)用了nofity方法, 喚醒在wait中的線程), 你現(xiàn)在需要洗澡的資源都有了, 萬(wàn)事俱備, 就差進(jìn)入浴室了, 這個(gè)時(shí)候你去浴室門口一看, 嘿, 浴室空著!(當(dāng)前沒(méi)有線程占用監(jiān)視器鎖) 舍友已經(jīng)洗好了! 于是你高高興興的帶著你的防脫洗發(fā)水進(jìn)去洗澡了(再次獲得監(jiān)視器鎖).
當(dāng)然, 上面還有另外一種情況, 假如你不知道快遞員什么時(shí)候會(huì)來(lái), 可能在一小時(shí)后, 也可能是明天, 那總不能一直干等著不洗澡吧, 于是你決定, 我就等一個(gè)小時(shí)(調(diào)用帶超時(shí)時(shí)間的wait(long timeout)方法), 一小時(shí)后快遞還不來(lái), 就不等了, 大不了用沐浴露湊合著洗洗頭 o(TヘTo)
上面只是拿生活中的例子打了個(gè)比方, 不知道大家理解了沒(méi)有, 下面我們就來(lái)正經(jīng)的看看代碼.
源碼分析以上5個(gè)都方法定義在了java的Object類中, 這意味著java中所有的類都會(huì)繼承這些方法.
同時(shí), 下面的源碼分析中我們將看到, 這些方法都是final類型的, 也就是說(shuō)所有的子類都不能改寫這些方法.
下面我們來(lái)看源碼:
(這一段會(huì)比較長(zhǎng), 不想看源碼分析的可以直接跳過(guò)這一部分看結(jié)論)
wait方法public final void wait() throws InterruptedException { wait(0); } public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final native void wait(long timeout) throws InterruptedException;
wait方法共有三個(gè), 我們發(fā)現(xiàn), 前兩個(gè)方法都是調(diào)用了最后一個(gè)方法, 而最后一個(gè)方法是一個(gè)native方法.
我們知道, native方法是非java代碼實(shí)現(xiàn)的, 我們看不到它的具體實(shí)現(xiàn)內(nèi)容, 但是java規(guī)定了該方法要實(shí)現(xiàn)什么樣的功能, 即它應(yīng)該在java代碼里"看起來(lái)是什么樣子的".
所以native方法就像java的接口一樣, 但是具體實(shí)現(xiàn)由JVM直接提供,或者(更多情況下)由外部的動(dòng)態(tài)鏈接庫(kù)(external dynamic link library)提供,然后被JVM調(diào)用。
在Object的源碼的注釋中, 描述了該native方法"看起來(lái)應(yīng)該是什么樣子的", 我們一段一段來(lái)看:
(這里我把原文也貼出來(lái)了, 是怕自己翻譯的不夠精確, 英語(yǔ)好的可以直接看原文)
/** * Causes the current thread to wait until either another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or a * specified amount of time has elapsed. ** The current thread must own this object"s monitor. *
... */
這段是說(shuō), 該方法導(dǎo)致了當(dāng)前線程掛起, 直到其他線程調(diào)用了這個(gè)object的 notify或者notifyAll方法, 或者設(shè)置的超時(shí)時(shí)間到了(超時(shí)時(shí)間即timeout參數(shù)的值, 以毫秒為單位), 另外它提到了, 當(dāng)前線程必須已經(jīng)拿到了監(jiān)視器鎖, 這點(diǎn)我們?cè)陂_篇的概論中已經(jīng)提到了.
/* ... * This method causes the current thread (call it T) to * place itself in the wait set for this object and then to relinquish * any and all synchronization claims on this object. Thread T * becomes disabled for thread scheduling purposes and lies dormant * until one of four things happens: *
這段話的大意是說(shuō), 該方法使得當(dāng)前線程進(jìn)入當(dāng)前監(jiān)視器鎖(this object)的等待隊(duì)列中(wait set), 并且放棄一切已經(jīng)擁有的(這個(gè)監(jiān)視器鎖上)的同步資源, 然后掛起當(dāng)前線程, 直到以下四個(gè)條件之一發(fā)生:
其他線程調(diào)用了this object的notify方法, 并且當(dāng)前線程恰好是被選中來(lái)喚醒的那一個(gè)(下面分析notify的時(shí)候我們就會(huì)知道, 該方法會(huì)隨機(jī)選擇一個(gè)線程去喚醒)
其他線程調(diào)用了this object的notifyAll方法,
其他線程中斷了(interrupt)了當(dāng)前線程
指定的超時(shí)時(shí)間到了.(如果指定的時(shí)間是0, 則該線程會(huì)一直等待, 直到收到其他線程的通知)
這里插一句, 關(guān)于第四條, 解釋了無(wú)參的wait方法:
public final void wait() throws InterruptedException { wait(0); }
我們知道, 無(wú)參的wait方法的超時(shí)時(shí)間就是0, 也就是說(shuō)他會(huì)無(wú)限期等待, 直到其他線程調(diào)用了notify
或者notifyAll.
同時(shí), 我們?cè)倏戳硪粋€(gè)有兩個(gè)參數(shù)的wait方法:
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
這個(gè)方法在其源碼的注釋中號(hào)稱是實(shí)現(xiàn)了納秒級(jí)別的更精細(xì)的控制:
/* *This method is similar to the {@code wait} method of one * argument, but it allows finer control over the amount of time to * wait for a notification before giving up. The amount of real time, * measured in nanoseconds, is given by: **** 1000000*timeout+nanos* In all other respects, this method does the same thing as the * method {@link #wait(long)} of one argument. In particular, * {@code wait(0, 0)} means the same thing as {@code wait(0)}. *
... */
但是我們實(shí)際看源碼可知, 當(dāng)nanos的值大于0但低于999999時(shí), 即低于1毫秒時(shí), 就直接將timeout++了, 所以這里哪里來(lái)的納秒級(jí)別的控制??? 最后不還是以毫秒為粒度嗎? 不過(guò)是多加一毫秒而已. 這個(gè)方法真的不是在賣萌嗎?(  ̄ー ̄)
注意, 這里同樣說(shuō)明了 wait(0,0) 與 wait(0)是等效的, 這點(diǎn)其實(shí)直接將值代入源碼也能得出這個(gè)結(jié)論.
好了, 吐槽完畢, 我們接著看剩下來(lái)的注釋:
/* ... * The thread T is then removed from the wait set for this * object and re-enabled for thread scheduling. It then competes in the * usual manner with other threads for the right to synchronize on the * object; once it has gained control of the object, all its * synchronization claims on the object are restored to the status quo * ante - that is, to the situation as of the time that the {@code wait} * method was invoked. Thread T then returns from the * invocation of the {@code wait} method. Thus, on return from the * {@code wait} method, the synchronization state of the object and of * thread {@code T} is exactly as it was when the {@code wait} method * was invoked. ... */
這一段說(shuō)的就是滿足了上面四個(gè)條件之一之后的事情了, 此時(shí)該線程會(huì)從wait set中移除, 重新參與到線程調(diào)度中, 并且和其他線程一樣, 競(jìng)爭(zhēng)鎖資源, 一旦它又獲得了監(jiān)視器鎖, 則它在調(diào)用wait方法時(shí)的所有狀態(tài)都會(huì)被恢復(fù), 即我們熟知的恢復(fù)現(xiàn)場(chǎng).
/* ... ** A thread can also wake up without being notified, interrupted, or * timing out, a so-called spurious wakeup. While this will rarely * occur in practice, applications must guard against it by testing for * the condition that should have caused the thread to be awakened, and * continuing to wait if the condition is not satisfied. In other words, * waits should always occur in loops, like this one: *
* synchronized (obj) { * while (* (For more information on this topic, see Section 3.2.3 in Doug Lea"s * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, * 2000), or Item 50 in Joshua Bloch"s "Effective Java Programming * Language Guide" (Addison-Wesley, 2001). ... */) * obj.wait(timeout); * ... // Perform action appropriate to condition * } *
這一段是說(shuō)即使沒(méi)有滿足上面4個(gè)條件之一, 線程也可能被喚醒, 稱之為假喚醒, 雖然這種情況很少出現(xiàn), 但是作者建議我們將wait放在循環(huán)體中, 并且檢測(cè)喚醒條件是不是真的滿足了, 并且還:
推薦了兩本書...
推薦了兩本書...
推薦了兩本書...
還愣著干嘛, 趕緊去買書呀(~ ̄(OO) ̄)ブ
/* ... *If the current thread is {@linkplain java.lang.Thread#interrupt() * interrupted} by any thread before or while it is waiting, then an * {@code InterruptedException} is thrown. This exception is not * thrown until the lock status of this object has been restored as * described above. ... */
這段解釋了中斷部分, 說(shuō)的是當(dāng)前線程在進(jìn)入wait set之前或者在wait set之中時(shí), 如果被其他線程中斷了, 則會(huì)拋出InterruptedException異常, 但是, 如果是在恢復(fù)現(xiàn)場(chǎng)的過(guò)程中被中斷了, 則直到現(xiàn)場(chǎng)恢復(fù)完成后才會(huì)拋出InterruptedException(這段不知道我理解的對(duì)不對(duì), 因?yàn)閷?duì)This exception is not thrown until the lock status of this object has been restored as described above.的翻譯不是很確信)
/* ... ** Note that the {@code wait} method, as it places the current thread * into the wait set for this object, unlocks only this object; any * other objects on which the current thread may be synchronized remain * locked while the thread waits. *
* This method should only be called by a thread that is the owner * of this object"s monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. */
這段話的意思是說(shuō), 即使wait方法把當(dāng)前線程放入this object的wait set里, 也只會(huì)釋放當(dāng)前監(jiān)視器鎖(this object), 如果當(dāng)前線程還持有了其他同步資源, 則即使當(dāng)前線程被掛起了, 也不會(huì)釋放這些資源.
同時(shí), 這里也提到, 該方法只能被已經(jīng)持有了監(jiān)視器鎖的線程所調(diào)用.
到這里, wait方法我們就分析完了, 雖然它是一個(gè)native方法, 源碼中并沒(méi)有具體實(shí)現(xiàn), 但是java規(guī)定了該方法的行為, 這些都體現(xiàn)了源碼的注釋中了.
同時(shí), 我們的分析中多次出現(xiàn)了 monitor, this object, wait set等術(shù)語(yǔ), 這些概念涉及到wait方法的實(shí)現(xiàn)細(xì)節(jié), 我們后面會(huì)講.
notify和notifyAll方法都是native方法:
public final native void notify(); public final native void notifyAll();
相比于wait方法, 這兩個(gè)方法的源碼注釋要少一點(diǎn), 我們就不分段看了, 直接看全部的
notify/** * Wakes up a single thread that is waiting on this object"s * monitor. If any threads are waiting on this object, one of them * is chosen to be awakened. The choice is arbitrary and occurs at * the discretion of the implementation. A thread waits on an object"s * monitor by calling one of the {@code wait} methods. ** The awakened thread will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened thread will * compete in the usual manner with any other threads that might be * actively competing to synchronize on this object; for example, the * awakened thread enjoys no reliable privilege or disadvantage in being * the next thread to lock this object. *
* This method should only be called by a thread that is the owner * of this object"s monitor. A thread becomes the owner of the * object"s monitor in one of three ways: *
* Only one thread at a time can own an object"s monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of this object"s monitor. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */
上面這段是說(shuō):
notify方法會(huì)在所有等待監(jiān)視器鎖的線程中任意選一個(gè)喚醒, 具體喚醒哪一個(gè), 交由該方法的實(shí)現(xiàn)者自己決定.
被喚醒的線程只有等到當(dāng)前持有鎖的線程完全釋放了鎖才能繼續(xù).(這里解釋下, 因?yàn)檎{(diào)用notify方法時(shí), 線程還在同步代碼塊里面, 只有離開了同步代碼塊, 鎖才會(huì)被釋放)
被喚醒的線程和其他所有競(jìng)爭(zhēng)這個(gè)監(jiān)視器鎖的線程地位是一樣的, 既不享有優(yōu)先權(quán), 也不占劣勢(shì).
這個(gè)方法應(yīng)當(dāng)只被持有監(jiān)視器鎖的線程調(diào)用, 一個(gè)線程可以通過(guò)以下三種方法之一獲得this object的監(jiān)視器鎖:
通過(guò)執(zhí)行該對(duì)象的普通同步方法
通過(guò)執(zhí)行synchonized代碼塊, 該代碼塊以this object作為鎖
通過(guò)執(zhí)行該類的靜態(tài)同步方法
我們通過(guò)上一篇介紹synchronized同步代碼塊的文章知道, synchronized作用于類的靜態(tài)方法時(shí), 是拿類的Class對(duì)象作為鎖, 作用于類的普通方法或者 synchronized(this){}代碼塊時(shí), 是拿當(dāng)前類的實(shí)例對(duì)象作為監(jiān)視器鎖, 這里的this object, 指的應(yīng)該是該線程調(diào)用notify方法所持有的鎖對(duì)象.
notifyAll/** * Wakes up all threads that are waiting on this object"s monitor. A * thread waits on an object"s monitor by calling one of the * {@code wait} methods. ** The awakened threads will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened threads * will compete in the usual manner with any other threads that might * be actively competing to synchronize on this object; for example, * the awakened threads enjoy no reliable privilege or disadvantage in * being the next thread to lock this object. *
* This method should only be called by a thread that is the owner * of this object"s monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of this object"s monitor. * @see java.lang.Object#notify() * @see java.lang.Object#wait() */
上面這段是說(shuō): notifyAll方法會(huì)喚醒所有等待this object監(jiān)視器鎖的線程, 其他內(nèi)容和notify一致.
總結(jié)總則: 調(diào)用這5個(gè)方法的線程必須持有監(jiān)視器鎖。
wait方法會(huì)使當(dāng)前線程進(jìn)入自己所持有的監(jiān)視器鎖(this object)的等待隊(duì)列中, 并且放棄一切已經(jīng)擁有的(這個(gè)監(jiān)視器鎖上的)同步資源, 然后掛起當(dāng)前線程, 直到以下四個(gè)條件之一發(fā)生:
其他線程調(diào)用了this object的notify方法, 并且當(dāng)前線程恰好是被選中來(lái)喚醒的那一個(gè)
其他線程調(diào)用了this object的notifyAll方法,
其他線程中斷了當(dāng)前線程
指定的超時(shí)時(shí)間到了.(如果指定的超時(shí)時(shí)間是0, 則該線程會(huì)一直等待, 直到收到其他線程的通知)
當(dāng)以上4個(gè)條件之一滿足后, 該線程從wait set中移除, 重新參與到線程調(diào)度中, 并且和其他線程一樣, 競(jìng)爭(zhēng)鎖資源, 一旦它又獲得了監(jiān)視器鎖, 則它在調(diào)用wait方法時(shí)的所有狀態(tài)都會(huì)被恢復(fù), 這里要注意“假喚醒”的問(wèn)題.
當(dāng)前線程在進(jìn)入wait set之前或者在wait set之中時(shí), 如果被其他線程中斷了, 則會(huì)拋出InterruptedException異常, 但是, 如果是在恢復(fù)現(xiàn)場(chǎng)的過(guò)程中被中斷了, 則直到現(xiàn)場(chǎng)恢復(fù)完成后才會(huì)拋出InterruptedException
即使wait方法把當(dāng)前線程放入this object的wait set里, 也只會(huì)釋放當(dāng)前監(jiān)視器鎖(this object), 如果當(dāng)前線程還持有了其他同步資源, 則即使它在this object中的等待隊(duì)列中, 也不會(huì)釋放.
notify方法會(huì)在所有等待監(jiān)視器鎖的線程中任意選一個(gè)喚醒, 具體喚醒哪一個(gè), 交由該方法的實(shí)現(xiàn)者自己決定.
線程調(diào)用notify方法后不會(huì)立即釋放監(jiān)視器鎖,只有退出同步代碼塊后,才會(huì)釋放鎖(與之相對(duì),調(diào)用wait方法會(huì)立即釋放監(jiān)視器鎖)
線程被notify或notifyAll喚醒后會(huì)繼續(xù)和其他普通線程一樣競(jìng)爭(zhēng)鎖資源
思考題本篇中多次提到了monitor, this object, wait set等概念,這些都代表什么意思?
監(jiān)視器鎖到底是怎么獲取和釋放的?
我們將在下一篇文章討論這個(gè)問(wèn)題。
(完)
查看更多系列文章:系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/76742.html
摘要:為了拓展同步代碼塊中的監(jiān)視器鎖,開始,出現(xiàn)了接口,它實(shí)現(xiàn)了可定時(shí)可輪詢與可中斷的鎖獲取操作,公平隊(duì)列,以及非塊結(jié)構(gòu)的鎖。 前言 系列文章目錄 前面幾篇我們學(xué)習(xí)了synchronized同步代碼塊,了解了java的內(nèi)置鎖,并學(xué)習(xí)了監(jiān)視器鎖的wait/notify機(jī)制。在大多數(shù)情況下,內(nèi)置鎖都能很好的工作,但它在功能上存在一些局限性,例如無(wú)法實(shí)現(xiàn)非阻塞結(jié)構(gòu)的加鎖規(guī)則等。為了拓展同步代...
摘要:在從返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。就緒隊(duì)列存儲(chǔ)了將要獲得鎖的線程,阻塞隊(duì)列存儲(chǔ)了被阻塞的線程。當(dāng)線程呈狀態(tài),調(diào)用線程對(duì)象的方法會(huì)出現(xiàn)異常。在執(zhí)行同步代碼塊過(guò)程中,遇到異常而導(dǎo)致線程終止,鎖也會(huì)被釋放。 方法wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,wait()方法是Object類的方法,該方法用來(lái)將當(dāng)前線程置入預(yù)執(zhí)行隊(duì)列中,并且在wait()所在的代碼行處停止執(zhí)行,直...
摘要:本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問(wèn),線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對(duì)象的對(duì)象鎖的其他線程。 本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問(wèn),線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時(shí)...
摘要:當(dāng)多個(gè)線程訪問(wèn)實(shí)例時(shí),每個(gè)線程維護(hù)提供的獨(dú)立的變量副本。而則從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn)。在執(zhí)行同步代碼塊的過(guò)程中,遇到異常而導(dǎo)致線程終止。在執(zhí)行同步代碼塊的過(guò)程中,其他線程執(zhí)行了當(dāng)前對(duì)象的方法,當(dāng)前線程被暫停,但不會(huì)釋放鎖。 一、Thread.start()與Thread.run()的區(qū)別通過(guò)調(diào)用Thread類的start()方法來(lái)啟動(dòng)一個(gè)線程,這時(shí)此線程是處于就緒狀態(tài),并沒(méi)有...
摘要:線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)。但是,這個(gè)標(biāo)志已經(jīng)被第一個(gè)喚醒的線程清除了,所以其余醒來(lái)的線程將回到等待狀態(tài),直到下次信號(hào)到來(lái)。如果方法調(diào)用,而非,所有等待線程都會(huì)被喚醒并依次檢查信號(hào)值。 線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)。另一方面,線程通信使線程能夠等待其他線程的信號(hào)。 showImg(http://segmentfault.com/img/bVbPLD); 例...
閱讀 3790·2021-10-14 09:43
閱讀 3383·2021-08-25 09:38
閱讀 676·2019-08-30 15:55
閱讀 1469·2019-08-30 13:05
閱讀 2300·2019-08-29 16:05
閱讀 573·2019-08-29 12:58
閱讀 2869·2019-08-29 12:34
閱讀 3302·2019-08-26 12:15