免费爱碰视频在线观看,九九精品国产屋,欧美亚洲尤物久久精品,1024在线观看视频亚洲

      喜提JDK的BUG一枚!多線程的情況下請謹(jǐn)慎使用這個(gè)類的stream遍歷

      喜提JDK的BUG一枚!多線程的情況下請謹(jǐn)慎使用這個(gè)類的stream遍歷

      你好呀,我是歪歪。

      前段時(shí)間在 RocketMQ 的 ISSUE 里面沖浪的時(shí)候,看到一個(gè) pr,雖說是在 RocketMQ 的地盤上發(fā)現(xiàn)的,但是這個(gè)玩意吧,其實(shí)和 RocketMQ 沒有任何關(guān)系。

      純純的就是 JDK 的一個(gè) BUG。

      我先問你一個(gè)問題:LinkedBlockingQueue 這個(gè)玩意是線程安全的嗎?

      這都是老八股文了,你要是不能脫口而出,應(yīng)該是要挨板子的。

      答案是:是線程安全的,因?yàn)橛羞@兩把鎖的存在。

      但是在 RocketMQ 的某個(gè)場景下,居然穩(wěn)定復(fù)現(xiàn)了 LinkedBlockingQueue 線程不安全的情況。

      先說結(jié)論:LinkedBlockingQueue 的 stream 遍歷的方式,在多線程下是有一定問題的,可能會(huì)出現(xiàn)死循環(huán)。

      老有意思了,這篇文章帶大家盤一盤。

      搞個(gè)Demo

      Demo 其實(shí)都不用我搞了,前面提到的 pr 的鏈接是這個(gè):

      https://github.com/apache/rocketmq/pull/3509

      在這個(gè)鏈接里面,前面圍繞著 RocketMQ 討論了很多。

      但是在中間部分,一個(gè)昵稱叫做 areyouok 的大佬一針見血,指出了問題的所在。

      直接給出了一個(gè)非常簡單的復(fù)現(xiàn)代碼。而且完全把 RocketMQ 的東西剝離了出去:

      正所謂前人栽樹后人乘涼,既然讓我看到了 areyouok 這位大佬的代碼,那我也就直接拿來當(dāng)做演示的 Demo 了。

      如果你不介意的話,為了表示我的尊敬,我斗膽說一聲:感謝雷總的代碼。

      我先把雷總的代碼粘出來,方便看文章的你也實(shí)際操作一把:

      public class TestQueue { public static void main(String[] args) throws Exception { LinkedBlockingQueue queue = new LinkedBlockingQueue(1000); for (int i = 0; i { while (true) { queue.offer(new Object()); queue.remove(); } }).start(); } while (true) { System.out.println(“begin scan, i still alive”); queue.stream() .filter(o -> o == null) .findFirst() .isPresent(); Thread.sleep(100); System.out.println(“finish scan, i still alive”); } }}

      介紹一下上面的代碼的核心邏輯。

      首先是搞了 10 個(gè)線程,每個(gè)線程里面在不停的調(diào)用 offer 和 remove 方法。

      需要注意的是這個(gè) remove 方法是無參方法,意思是移除頭節(jié)點(diǎn)。

      再強(qiáng)調(diào)一次:LinkedBlockingQueue 里面有 ReentrantLock 鎖,所以即使多個(gè)線程并發(fā)操作 offer 或者 remove 方法,也都要分別拿到鎖才能操作,所以這一定是線程安全的。

      然后主線程里面搞個(gè)死循環(huán),對 queue 進(jìn)行 stream 操作,看看能不能找到隊(duì)列里面第一個(gè)不為空的元素。

      這個(gè) stream 操作是一個(gè)障眼法,真正的關(guān)鍵點(diǎn)在于 tryAdvance 方法:

      先在這個(gè)方法這里插個(gè)眼,一會(huì)再細(xì)嗦它。

      按理來說,這個(gè)方法運(yùn)行起來之后,應(yīng)該不停的輸出這兩句話才對:

      begin scan, i still alivefinish scan, i still alive

      但是,你把代碼粘出去用 JDK 8 跑一把,你會(huì)發(fā)現(xiàn)控制臺只有這個(gè)玩意:

      或者只交替輸出幾次就沒了。

      但是當(dāng)我們不動(dòng)代碼,只是替換一下 JDK 版本,比如我剛好有個(gè) JDK 15,替換之后再次運(yùn)行,交替的效果就出來了:

      那么基于上面的表現(xiàn),我是不是可以大膽的猜測,這是 JDK 8 版本的 BUG 呢?

      現(xiàn)在我們有了能在 JDK 8 運(yùn)行環(huán)境下穩(wěn)定復(fù)現(xiàn)的 Demo,接下來就是定位 BUG 的原因了。

      啥原因呀?

      先說一下我拿到這個(gè)問題之后,排查的思路。

      非常的簡單,你想一想,主線程應(yīng)該一直輸出但是卻沒有輸出,那么它到底是在干什么呢?

      我初步懷疑是在等待鎖。

      怎么去驗(yàn)證呢?

      朋友們,可愛的小相機(jī)又出現(xiàn)了:

      通過它我可以 Dump 當(dāng)前狀態(tài)下各個(gè)線程都在干嘛。

      但是當(dāng)我看到主線程的狀態(tài)是 RUNNABLE 的時(shí)候,我就有點(diǎn)懵逼了:

      啥情況???

      如果是在等待鎖,不應(yīng)該是 RUNNABLE 啊?

      再來 Dump 一次,驗(yàn)證一下:

      發(fā)現(xiàn)還是在 RUNNABLE,那么直接就可以排除鎖等待的這個(gè)懷疑了。

      我專門體現(xiàn)出兩次 Dump 線程的這個(gè)操作,是有原因的。

      因?yàn)楹芏嗯笥言?Dump 線程的時(shí)候拿著一個(gè) Dump 文件在哪兒使勁分析,但是我覺得正確的操作應(yīng)該是在不同時(shí)間點(diǎn)多次 Dump,對比分析不同 Dump 文件里面的相同線程分別是在干啥。

      比如我兩次不同時(shí)間點(diǎn) Dump,發(fā)現(xiàn)主線程都是 RUNNABLE 狀態(tài),那么說明從程序的角度來說,主線程并沒有阻塞。

      但是從控制臺輸出的角度來說,它似乎又是阻塞住了。

      經(jīng)典啊,朋友們。你想想這是什么經(jīng)典的畫面?。?/p>

      這不就是,這個(gè)玩意嗎,線程里面有個(gè)死循環(huán):

      System.out.println(“begin scan, i still alive”);while (true) {}System.out.println(“finish scan, i still alive”);

      來驗(yàn)證一波。

      從 Dump 文件中我們可以觀察到的是主線程正在執(zhí)行這個(gè)方法:

      at java.util.concurrent.LinkedBlockingQueue$LBQSpliterator.tryAdvance(LinkedBlockingQueue.java:950)

      還記得我前面插的眼嗎?

      這里就是我前面說的 stream 只是障眼法,真正關(guān)鍵的點(diǎn)在于 tryAdvance 方法。

      點(diǎn)過去看一眼 JDK 8 的 tryAdvance 方法,果不其然,里面有一個(gè) while 循環(huán):

      從 while 條件上看是 current!=null 一直為ture,且 e!=null 一直為 false,所以跳不出這個(gè)循環(huán)。

      但是從 while 循環(huán)體里面的邏輯來看,里面的 current 節(jié)點(diǎn)是會(huì)發(fā)生變化的:

      current = current.next;

      來,結(jié)合這目前有的這幾個(gè)條件,我來細(xì)嗦一下。

      • LinkedBlockingQueue 的數(shù)據(jù)結(jié)果是鏈表。
      • 在 tryAdvance 方法里面出現(xiàn)了死循環(huán),說明循環(huán)條件 current=null 一直是 true,e!=null 一直為 false。
      • 但是循環(huán)體里面有獲取下一節(jié)點(diǎn)的動(dòng)作,current = current.next。

      綜上可得,當(dāng)前這個(gè)鏈表中有一個(gè)節(jié)點(diǎn)是這樣的:

      只有這樣,才會(huì)同時(shí)滿足這兩個(gè)條件:

      • current.item=null
      • current.next=null

      那么什么時(shí)候才會(huì)出現(xiàn)這樣的節(jié)點(diǎn)呢?

      這個(gè)情況就是把節(jié)點(diǎn)從鏈表上拿掉,所以肯定是調(diào)用移除節(jié)點(diǎn)相關(guān)的方法的時(shí)候。

      縱觀我們的 Demo 代碼,里面和移除相關(guān)的代碼就這一行:

      queue.remove();

      而前面說了,這個(gè) remove 方法是移除頭節(jié)點(diǎn),效果和 poll 是一樣一樣的,它的源碼里面也是直接調(diào)用了 poll 方法:

      所以我們主要看一下 poll 方法的源碼:

      java.util.concurrent.LinkedBlockingQueue#poll()

      兩個(gè)標(biāo)號為 ① 的地方分別是拿鎖和釋放鎖,說明這個(gè)方法是線程安全的。

      然后重點(diǎn)是標(biāo)號為 ② 的地方,這個(gè) dequeue 方法,這個(gè)方法就是移除頭節(jié)點(diǎn)的方法:

      java.util.concurrent.LinkedBlockingQueue#dequeue

      它是怎么移除頭節(jié)點(diǎn)的呢?

      就是我框起來的部分,自己指向自己,做一個(gè)性格孤僻的節(jié)點(diǎn),就完事了。

      h.next=h

      也就是我前面畫的這個(gè)圖:

      那么 dequeue 方法的這個(gè)地方和 tryAdvance 方法里面的 while 循環(huán)會(huì)發(fā)生一個(gè)什么樣神奇的事情呢?

      這玩意還不好描述,你知道吧,所以,我決定下面給你畫個(gè)圖,理解起來容易一點(diǎn)。

      畫面演示

      現(xiàn)在我已經(jīng)掌握到這個(gè) BUG 的原理了,所以為了方便我 Debug,我把實(shí)例代碼也簡化一下,核心邏輯不變,還是就這么幾行代碼,主要還是得觸發(fā) tryAdvance 方法:

      首先根據(jù)代碼,當(dāng) queue 隊(duì)列添加完元素之后,隊(duì)列是長這樣的:

      畫個(gè)示意圖是這樣的:

      然后,我們接著往下執(zhí)行遍歷的操作,也就是觸發(fā) tryAdvance 方法:

      上面的圖我專門多截了一個(gè)方法。

      就是如果往上再看一步,觸發(fā) tryAdvance 方法的地方叫做 forEachWithCancel ,從源碼上看其實(shí)也是一個(gè)循環(huán),循環(huán)結(jié)束條件是 tryAdvance 方法返回為 false ,意思是遍歷結(jié)束了。

      然后我還特意把加鎖和解鎖的地方框起來了,意思是說明 try 方法是線程安全的,因?yàn)檫@個(gè)時(shí)候把 put 和 take 的鎖都拿到了。

      說人話就是,當(dāng)某個(gè)線程在執(zhí)行 tryAdvance 方法,且加鎖成功之后,如果其他線程需要操作隊(duì)列,那么是獲取不到鎖的,必須等這個(gè)線程操作完成并釋放鎖。

      但是加鎖的范圍不是整個(gè)遍歷期間,而是每次觸發(fā) tryAdvance 方法的時(shí)候。

      而每次 tryAdvance 方法,只處理鏈表中的一個(gè)節(jié)點(diǎn)。

      到這里鋪墊的差不多了,接下來我就帶你逐步的分析一下 tryAdvance 方法的核心源碼,也就是這部分代碼:

      第一次觸發(fā)的時(shí)候,current 對象是 null,所以會(huì)執(zhí)行一個(gè)初始化的東西:

      current = q.head.next;

      那么這個(gè)時(shí)候 current 就是 節(jié)點(diǎn) 1:

      。

      接著執(zhí)行 while 循環(huán),這時(shí) current!=null 條件滿足,進(jìn)入循環(huán)體。

      在循環(huán)體里面,會(huì)執(zhí)行兩行代碼。

      第一行是這個(gè),取出當(dāng)前節(jié)點(diǎn)里面的值:

      e = current.item;

      在我的 Demo 里面,e=1。

      第二行是這行代碼,含義是維護(hù) current 為下一節(jié)點(diǎn),等著下次 tryAdvance 方法觸發(fā)的時(shí)候直接拿來用:

      current = current.next;

      接著因?yàn)?e!=null,所以 break 結(jié)束循環(huán):

      第一次 tryAdvance 方法執(zhí)行完成之后,current 指向的是這個(gè)位置的節(jié)點(diǎn):

      朋友們,接下來有意思的就來了。

      假設(shè)第二次 tryAdvance 方法觸發(fā)的時(shí)候,執(zhí)行到下面框起來的部分的任意一行代碼,也就是還沒有獲取鎖或者獲取不到鎖的時(shí)候:

      這時(shí)候有另外一個(gè)線程來了,它在執(zhí)行 remove() 方法,不斷的移除頭結(jié)點(diǎn)。

      執(zhí)行三次 remove() 方法之后,鏈表就變成了這樣:

      接下來,當(dāng)我把這兩個(gè)圖合并在一起的時(shí)候,就是見證奇跡的時(shí)候:

      當(dāng)?shù)谌螆?zhí)行 remover 方法后,tryAdvance 方法再次成功搶到鎖,開始執(zhí)行,從我們的上帝視角,看到的是這樣的場景:

      這一點(diǎn),我可以從 Debug 的視圖里面進(jìn)行驗(yàn)證:

      可以看到,current 的 next 節(jié)點(diǎn)還是它自己,而且它們都是 LinkedBlockingQueue$Mode@701 這個(gè)對象,并不為 null。

      所以這個(gè)地方的死循環(huán)就是這么來的。

      分析完了之后,你再回想一下這個(gè)過程,其實(shí)這個(gè)問題是不是并沒有想象的那么困難。

      你要相信,只要給到你能穩(wěn)定復(fù)現(xiàn)的代碼,一切 BUG 都是能夠調(diào)試出來的。

      我在調(diào)試的過程中,還想到了另外一個(gè)問題:如果我調(diào)用的是這個(gè) remove 方法呢,移除指定元素。

      會(huì)不會(huì)出現(xiàn)一樣的問題呢?

      我也不知道,但是很簡單,實(shí)驗(yàn)一把就知道了。

      還是在 tryAdvance 方法里面打上斷點(diǎn),然后在第二次觸發(fā) tryAdvance 方法之后,通過 Alt+F8 調(diào)出 Evaluate 功能,分別執(zhí)行 queue.remove 1,2,3:

      然后觀察 current 元素,并沒有出現(xiàn)自己指向自己的情況:

      為什么呢?

      源碼之下無秘密。

      答案就寫在 unlink 方法里面:

      入?yún)⒅械?p 是要移除的節(jié)點(diǎn),而 trail 是要移除的節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)。

      在源碼里面只看到了 trail.next=p.next,也就是通過指針,跳過要移除的節(jié)點(diǎn)。

      但是并沒有看到前面 dequeue 方法中出現(xiàn)的類似于 p.next=p 的源碼,也就是把節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)指向自己的動(dòng)作。

      為什么?

      作者都在注釋里面給你寫清楚了:

      p.next is not changed, to allow iterators that are traversing p to maintain their weak-consistency guarantee.p.next 沒有發(fā)生改變,因?yàn)樵谠O(shè)計(jì)上是為了保持正在遍歷 p 的迭代器的弱一致性。

      說人話就是:這玩意不能指向自己啊,指向自己了要是這個(gè)節(jié)點(diǎn)正在被迭代器執(zhí)行,那不是完?duì)僮恿藛幔?/p>

      所以帶參的 remove 方法是考慮到了迭代器的情況,但是無參的 remove 方法,確實(shí)考慮不周。

      怎么修復(fù)的?

      我在 JDK 的 BUG 庫里面搜了一下,其實(shí)這個(gè)問題 2016 年就出現(xiàn)在了 JDK 的 BUG 列表里面:

      https://bugs.openjdk.org/browse/JDK-8171051

      在 JDK9 的版本里面完成了修復(fù)。

      我本地有一份 JDK15 的源碼,所以給你對比著 JDK8 的源碼看一下:

      主要的變化是在 try 的代碼塊里面。

      JDK15 的源碼里面調(diào)用了一個(gè) succ 方法,從方法上的注釋也可以看出來就是專門修復(fù)這個(gè) BUG 的:

      比如回到這個(gè)場景下:

      我們來細(xì)嗦一下當(dāng)前這個(gè)情況下, succ 方法是怎么處理的:

      Node succ(Node p) { if (p == (p = p.next)) p = head.next; return p;}

      p 是上圖中的 current 對應(yīng)的元素。

      首先 p = p.next 還是 p,因?yàn)樗约褐赶蜃约毫?,這個(gè)沒毛病吧?

      那么 p == (p = p.next),帶入條件,就是 p==p,條件為 true,這個(gè)沒毛病吧?

      所以執(zhí)行 p = head.next,從上圖中來看,head.next 就是元素為 4 的這個(gè)節(jié)點(diǎn),沒毛病吧?

      最后取到了元素 4,也就是最后一個(gè)元素,接著結(jié)束循環(huán):

      沒有死循環(huán),完美。

      延伸一下

      回到我這篇文章開篇的一個(gè)問題:LinkedBlockingQueue 這個(gè)玩意是線程安全的嗎?

      下次你面試的時(shí)候遇到這個(gè)問題,你就微微一笑,答到:由于內(nèi)部有讀寫鎖的存在,這個(gè)玩意一般情況下是線程安全的。但是,在 JDK8 的場景下,當(dāng)它遇到 stream 操作的時(shí)候,又有其他線程在調(diào)用無參的 remove 方法,會(huì)有一定幾率出現(xiàn)死循環(huán)的情況。

      說的時(shí)候自信一點(diǎn),一般情況下,可以唬一下面試官。

      前面我給的解決方案是升級 JDK 版本,但是你知道的,這是一個(gè)大動(dòng)作,一般來說,能跑就不要輕舉妄動(dòng),

      所以另外我還能想到兩個(gè)方案。

      第一個(gè)你就別用 stream 了唄,老老實(shí)實(shí)的使用迭代器循環(huán),它不香嗎?

      第二個(gè)方案是這樣的:

      效果杠杠的,絕對沒問題。

      你內(nèi)部的 ReentrantLock 算啥,我直接給你來個(gè)鎖提升,外部用 synchronized 給你包裹起來。

      來,你有本事再給我表演一個(gè)線程不安全。

      現(xiàn)在,我換一個(gè)問題問你:ConcurrentHashMap 是線程安全的嗎?

      我之前寫過,這玩意在 JDK8 下也是有死循環(huán)的《震驚!ConcurrentHashMap里面也有死循環(huán),作者留下的“彩蛋”了解一下?》

      在文章的最后我也問了一樣的問題。

      當(dāng)時(shí)的回答再次搬運(yùn)一下:

      是的,ConcurrentHashMap 本身一定是線程安全的。但是,如果你使用不當(dāng)還是有可能會(huì)出現(xiàn)線程不安全的情況。

      給大家看一點(diǎn) Spring 中的源碼吧:

      org.springframework.core.SimpleAliasRegistry

      在這個(gè)類中,aliasMap 是 ConcurrentHashMap 類型的:

      在 registerAlias 和 getAliases 方法中,都有對 aliasMap 進(jìn)行操作的代碼,但是在操作之前都是用 synchronized 把 aliasMap 鎖住了。

      為什么我們操作 ConcurrentHashMap 的時(shí)候還要加鎖呢?

      這個(gè)是根據(jù)場景而定的,這個(gè)別名管理器,在這里加鎖應(yīng)該是為了避免多個(gè)線程操作 ConcurrentHashMap 。

      雖然 ConcurrentHashMap 是線程安全的,但是假設(shè)如果一個(gè)線程 put,一個(gè)線程 get,在這個(gè)代碼的場景里面是不允許的。

      具體情況,需要具體分析。

      如果覺得不太好理解的話我舉一個(gè) Redis 的例子。

      Redis 的 get、set 方法都是線程安全的吧。但是你如果先 get 再 set,那么在多線程的情況下還是會(huì)有問題的。

      因?yàn)檫@兩個(gè)操作不是原子性的。所以 incr 就應(yīng)運(yùn)而生了。

      我舉這個(gè)例子的是想說線程安全與否不是絕對的,要看場景。給你一個(gè)線程安全的容器,你使用不當(dāng)還是會(huì)有線程安全的問題。

      再比如,HashMap 一定是線程不安全的嗎?

      說不能說的這么死吧。它是一個(gè)線程不安全的容器。但是如果我的使用場景是只讀呢?

      在這個(gè)只讀的場景下,它就是線程安全的。

      總之,看場景,不要脫離場景討論問題。

      道理,就是這么一個(gè)道理。

      最后,再說一次結(jié)論:LinkedBlockingQueue 的 stream 遍歷的方式,在多線程下是有一定問題的,可能會(huì)出現(xiàn)死循環(huán)。

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶投稿
      上一篇 2022年6月15日 15:19
      下一篇 2022年6月15日 15:19

      相關(guān)推薦

      • 分享4條發(fā)微商朋友圈的方法(微商朋友圈應(yīng)該怎么發(fā))

        對于微商朋友來說,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底該怎么發(fā)呢? 為什么同樣是經(jīng)營一個(gè)朋友圈,有的微商看起來逼格滿滿,實(shí)際效果也不錯(cuò);而有的卻動(dòng)都不動(dòng)就被屏蔽甚至拉黑…

        2022年11月27日
      • 《寶可夢朱紫》夢特性怎么獲得?隱藏特性獲取方法推薦

        寶可夢朱紫里有很多寶可夢都是擁有夢特性會(huì)變強(qiáng)的寶可夢,很多玩家不知道夢特性怎么獲得,下面就給大家?guī)韺毧蓧糁熳想[藏特性獲取方法推薦,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 …

        2022年11月25日
      • 《寶可夢朱紫》奇魯莉安怎么進(jìn)化?奇魯莉安進(jìn)化方法分享

        寶可夢朱紫中的奇魯莉安要怎么進(jìn)化呢?很多玩家都不知道,下面就給大家?guī)韺毧蓧糁熳掀骠斃虬策M(jìn)化方法分享,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 奇魯莉安進(jìn)化方法分享 奇魯莉安…

        2022年11月25日
      • 淘寶直播開通后帶貨鏈接怎么做(淘寶直播需要開通淘寶店鋪嗎)

        直播帶貨無論是對于商家來說還是主播收益都是非??捎^的,所以不少平臺都有直播帶貨功能,一些小伙伴也想加入淘寶直播,那么淘寶直播開通后帶貨鏈接怎么做?下面小編為大家?guī)硖詫氈辈ラ_通后帶…

        2022年11月24日
      • cpu性能天梯圖2022 AMD CPU天梯圖最新排行榜出爐

        用戶在DIY自己的主機(jī)時(shí)選擇CPU是非常關(guān)鍵的,CPU可以說是電腦的大腦,大家也都想追求好一點(diǎn)的CPU來使用,但型號太多了,大部分的用戶都不知道目前哪一款CPU比較好用,快來看看詳…

        2022年11月24日
      • 《寶可夢朱紫》暴飛龍?jiān)趺醋??暴飛龍獲得方法

        寶可夢朱紫暴飛龍位置在哪?在游戲中,很多玩家還不清楚暴飛龍具體要怎么樣獲得,其實(shí)獲得方法很簡單,暴飛龍直接是沒得抓的,需要玩家從寶貝龍進(jìn)化得到,下面一起來看一下寶可夢朱紫暴飛龍獲得…

        2022年11月23日
      • 《寶可夢朱紫》布土撥怎么進(jìn)化?布土撥進(jìn)化方法介紹

        寶可夢朱紫中,不同的寶可夢有不同的進(jìn)化方法,其中布土撥的進(jìn)化方法是比較特殊的。很多玩家不知道寶可夢朱紫布土撥怎么進(jìn)化,下面就帶來寶可夢朱紫布土撥進(jìn)化方法介紹,一起來看看吧,希望能幫…

        2022年11月23日
      • 《寶可夢朱紫》薄荷怎么獲得?薄荷獲得方法

        寶可夢朱紫中薄荷有改變寶可夢的屬性或性格等效果,很多玩家想知道寶可夢朱紫薄荷怎么獲得,下面就帶來寶可夢朱紫薄荷獲得方法,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 薄荷獲得方法…

        2022年11月23日
      • 《寶可夢朱紫》怎么交換精靈?交換精靈方法一覽

        寶可夢朱紫中玩家可以和好友或者npc進(jìn)行交換寶可夢獲得自己沒有的寶可夢,很多玩家想知道寶可夢朱紫怎么交換精靈,下面就帶來寶可夢朱紫交換精靈方法一覽,感興趣的小伙伴不要錯(cuò)過,希望能幫…

        2022年11月23日
      • 《寶可夢朱紫》龍爪技能怎么獲得?龍爪技能獲取方法

        寶可夢朱紫龍爪技能怎么獲得?在游戲中,很多玩家還不清楚龍爪技能應(yīng)該怎么獲取,其實(shí)獲取方法有很多,下面一起來看一下寶可夢朱紫龍爪技能獲取方法,希望可以幫助各位玩家順利的進(jìn)行游戲內(nèi)容?!?/p>

        2022年11月23日

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息