黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

Effective Java (并發(fā))

系統(tǒng) 2197 0

六十六、同步訪問共享的可變數(shù)據(jù):

?? ?? 在Java中很多時(shí)候都是通過synchronized關(guān)鍵字來實(shí)現(xiàn)共享對(duì)象之間的同步的。事實(shí)上,對(duì)象同步并不僅限于當(dāng)多個(gè)線程操作同一可變對(duì)象時(shí),仍然能夠保證該共享對(duì)象的狀態(tài)始終保持一致。與此同時(shí),他還可以保證進(jìn)入同步方法或者同步代碼塊的每個(gè)線程,都看到由同一個(gè)鎖保護(hù)的之前所有的修改效果。
?? ?? Java的語言規(guī)范保證了讀寫一個(gè)變量是原子的,除非這個(gè)變量的類型為long或double。換句話說,讀取一個(gè)非long或double類型的變量,可以保證返回的值是某個(gè)線程保存在該變量中的,即時(shí)多個(gè)線程在沒有同步的情況下并發(fā)地修改這個(gè)變量也是如此。然而需要特別指出的是,這樣的做法是非常危險(xiǎn)的。即便這樣做不會(huì)帶來數(shù)據(jù)同步修改的問題,但是他會(huì)導(dǎo)致另外一個(gè)更為隱匿的錯(cuò)誤發(fā)生。見如下代碼:

1 ???? public class StopThread {
2 ???????? private static boolean stopRequested = false ;
3 ???????? public static void main(String[] args) throw InterruptedException {
4 ???????????? Thread bgThread = new Thread( new Runnable() {
5 ???????????????? public void run() {
6 ???????????????????? int i = 0;
7 ???????????????????? while (!stopRequested)
8 ???????????????????????? i++;
9 ???????????????? }
10 ???????????? });
11 ???????????? bgThread.start();
12 ???????????? TimeUnit.SECONDS.sleep(1);
13 ???????????? stopRequested = true ;
14 ???????? }
15 ???? }
?

?? ?? 對(duì)于上面的代碼片段,有些人會(huì)認(rèn)為在主函數(shù)sleep一秒后,工作者線程的循環(huán)狀態(tài)標(biāo)志(stopRequested)就會(huì)被修改,從而致使工作者線程正常退出。然而事實(shí)卻并非如此,因?yàn)镴ava的規(guī)范中并沒有保證在非同步狀態(tài)下,一個(gè)線程修改的變量,在另一個(gè)線程中就會(huì)立即可見。事實(shí)上,這也是Java針對(duì)內(nèi)存模型進(jìn)行優(yōu)化的一個(gè)技巧。為了把事情描述清楚,我們可以將上面代碼中run方法的代碼模擬為優(yōu)化后的代碼,見如下修改后的run方法:

1 public void run() {
2 ???????? int i = 0;
3 ???????? if (!stopRequested) {
4 ???????????? while ( true )
5 ???????????????? i++;
6 ???????? }
7 ???? }
?

?? ?? 這種優(yōu)化被稱為提升,正是HotSpot Server VM的工作。
?? ?? 要解決這個(gè)問題并不難,只需在讀取和寫入stopRequested的時(shí)候加入synchronized關(guān)鍵字即可,見如下代碼:

復(fù)制代碼
1 ???? public class StopThread {
2 ???????? private static boolean stopRequested = false ;
3 ???????? private static synchronized void requestStop() {
4 ???????????? stopRequested = true ;
5 ???????? }
6 ???????? private static synchronized boolean stopRequested() {
7 ???????????? return stopRequested;
8 ???????? }
9 ???????? public static void main(String[] args) throw InterruptedException {
10 ???????????? Thread bgThread = new Thread( new Runnable() {
11 ???????????????? public void run() {
12 ???????????????????? int i = 0;
13 ???????????????????? while (!stopRequested())
14 ???????????????????????? i++;
15 ???????????????? }
16 ???????????? });
17 ???????????? bgThread.start();
18 ???????????? TimeUnit.SECONDS.sleep(1);
19 ???????????? requestStop();
20 ???????? }
21 ???? }
?

????? 在上面的修改代碼中,讀寫該變量的函數(shù)均被加以同步。
?? ?? 事實(shí)上,Java中還提供了另外一種方式用于處理該類問題,即volatile關(guān)鍵字。該單詞的直譯為“易變的”,引申到這里就是告訴cpu該變量是容易被改變的變量,不能每次都從當(dāng)前線程的內(nèi)存模型中獲取該變量的值,而是必須從主存中獲取,這種做法所帶來的唯一負(fù)面影響就是效率的折損,但是相比于synchronized關(guān)鍵字,其效率優(yōu)勢還是非常明顯的。見如下代碼:

1 ???? public class StopThread {
2 ???????? private static volatile boolean stopRequested = false ;
3 ???????? public static void main(String[] args) throw InterruptedException {
4 ???????????? Thread bgThread = new Thread( new Runnable() {
5 ???????????????? public void run() {
6 ???????????????????? int i = 0;
7 ???????????????????? while (!stopRequested)
8 ???????????????????????? i++;
9 ???????????????? }
10 ???????????? });
11 ???????????? bgThread.start();
12 ???????????? TimeUnit.SECONDS.sleep(1);
13 ???????????? stopRequested = true ;
14 ???????? }
15 ???? }
?

????? 和第一個(gè)代碼片段相比,這里只是在stopRequested域變量聲明之前加上volatile關(guān)鍵字,從而保證該變量為易變變量。然而需要說明的是,該關(guān)鍵字并不能完全取代synchronized同步方式,見如下代碼:

        
          1 
        
        
          public
        
        
          class
        
         Test {


        
          2
        
        
          private
        
        
          static
        
        
          volatile
        
        
          int
        
         nextID = 0;


        
          3
        
        
          public
        
        
          static
        
        
          int
        
         generateNextID() {

        
          
4
        
        
          return
        
         nextID++;

        
          
5
        
                 }


        
          6
        
             }
      

?? ?? generateNextID方法的用意為每次都給調(diào)用者生成不同的ID值,遺憾的是,最終結(jié)果并不是我們期望的那樣,當(dāng)多個(gè)線程調(diào)用該方法時(shí),極有可能出現(xiàn)重復(fù)的ID值。這是因?yàn)?+運(yùn)算符并不是原子操作,而是由兩個(gè)指令構(gòu)成,首先是讀取該值,加一之后再重新賦值。由此可見,這兩個(gè)指令之間的時(shí)間窗口極有可能造成數(shù)據(jù)的不一致。如果要修復(fù)該問題,我們可以使用JDK(1.5 later)中java.util.concurrent.atomic包提供的AtomicLong類,使用該類性能要明顯好于synchronized的同步方式,見如下修復(fù)后的代碼:

復(fù)制代碼
1 ???? public class Test {
2 ???????? private static final AtomicLong nextID = new AtomicLong();
3 ???????? public static long generateNextID() {
4 ???????????? return nextID.getAndIncrement();
5 ???????? }
6 ???? }

??? ?
六十七、避免過度同步:

?? ?? 過度同步所導(dǎo)致的最明顯問題就是性能下降,特別是在如今的多核時(shí)代,再有就是可能引發(fā)的死鎖和一系列不確定性的問題。當(dāng)同步函數(shù)或同步代碼塊內(nèi)調(diào)用了外來方法,如可被子類覆蓋的方法,或外部類的接口方法等。由于這些方法的行為存在一定的未知性,如果在同步塊內(nèi)調(diào)用了類似的方法,將極有可能給當(dāng)前的同步帶來未知的破壞性。見如下代碼:

1 ???? public class ObservableSet<E> extends ForwardingSet<E> {
2 ???????? public ObservableSet(Set<E> set) {
3 ???????????? super (set);
4 ???????? }
5 ???????? private final List<SetObserver<E>> observers = new ArrayList<SetObserver<E>>();
6 ???????? public void addObserver(SetObserver<E> observer) {
7 ???????????? synchronized (observers) {
8 ???????????????? observers.add(observer);
9 ???????????? }
10 ???????? }
11 ???????? public boolean removeObserver(SetObserver<E> observer) {
12 ???????????? synchronized (observers) {
13 ???????????????? return observers.remover(observer);
14 ???????????? }
15 ???????? }
16 ???????? private void notifyElementAdded(E element) {
17 ???????????? synchronized (observers) {
18 ???????????????? for (SetObserver<E> observer : observers)
19 ???????????????????? observer.added( this ,element);
20 ???????????? }
21 ???????? }
22 ???????? @Override public boolean add(E element) {
23 ???????????? boolean added = super .add(element);
24 ???????????? if (added)
25 ???????????????? notifyElementAdded(element);
26 ???????????? return added;
27 ???????? }
28 ???????? @Override public boolean addAll(Collection<? extends E> c) {
29 ???????????? boolean result = false ;
30 ???????????? for (E element : c)
31 ???????????????? result |= add(element);
32 ???????????? return result;
33 ???????? }
34 ???? }
?

?? ?? 下面的代碼片段是回調(diào)接口和測試調(diào)用:

1 ???? public interface SetObserver<E> {
2 ???????? void added(ObservableSet<E> set,E element);
3 ???? }
4 ????
5 ???? public static void main(String[] args) {
6 ???????? ObservableSet<Integer> set = new ObservableSet<Integer>( new HashSet<Integer>());
7 ???????? set.addObserver( new SetObserver<Integer>() {
8 ???????????? public void added(ObservableSet<Integer> s, Integer e) {
9 ???????????????? System.out.println(e);
10 ???????????? }
11 ???????? });
12 ???????? for ( int i = 0; i < 100; i++)
13 ???????????? set.add(i);
14 ???? }
?

????? 對(duì)于這個(gè)測試用例,他完全沒有問題,可以保證得到正確的輸出,即打印出0-99的數(shù)字。
?? ?? 現(xiàn)在我們換一個(gè)觀察者接口的實(shí)現(xiàn)方式,見如下代碼片段:

1 ???? set.addObserver( new SetObserver<Integer>() {
2 ???????? public void added(ObservableSet<Integer> s,Integer e) {
3 ???????????? System.out.println(e);
4 ???????????? if (e == 23)
5 ???????????????? s.removeObserver( this );
6 ???????? }
7 ???? });
?

?? ?? 對(duì)于以上代碼,當(dāng)執(zhí)行s.removeObserver(this)的時(shí)候,將會(huì)拋出ConcurrentModificationException異常,因?yàn)樵趎otifyElementAdded方法中正在遍歷該集合。對(duì)于該段代碼,我只能說我們是幸運(yùn)的,錯(cuò)誤被及時(shí)拋出并迅速定位,這是因?yàn)槲覀兊恼{(diào)用是在同一個(gè)線程內(nèi)完成的,而Java中synchronized關(guān)鍵字構(gòu)成的鎖是可重入的,或者說是可遞歸的,即在同一個(gè)線程內(nèi)可多次調(diào)用且不會(huì)被阻塞。如果恰恰相反,我們的沖突調(diào)用來自于多個(gè)線程,那么將會(huì)形成死鎖。在多線程的應(yīng)用程序中,死鎖是一種比較難以重現(xiàn)和定位的錯(cuò)誤。為了解決上述問題,我們需要做的一是將調(diào)用外部代碼的部分移出同步代碼塊,再有就是針對(duì)該遍歷,我們需要提前copy出來一份,并基于該對(duì)象進(jìn)行遍歷,從而避免了上面的并發(fā)訪問沖突,如:

復(fù)制代碼
1 ???? private void notifyElementAdded(E element) {
2 ???????? List<SetObserver<E>> snapshot = null ;
3 ???????? synchronized (observers) {
4 ???????????? snapshot = new ArrayList<SetObserver<E>>(observers);
5 ???????? }
6 ???????? for (SetObserver<E> Observer : snapshot)
7 ???????????? Observer.added( this ,element);
8 ???? }
?

????? 減少不必要的代碼同步還可以大大提高程序的并發(fā)執(zhí)行效率,一個(gè)非常明顯的例子就是StringBuffer,該類在JDK的早期版本中即以出現(xiàn),是數(shù)據(jù)操作同步類,即時(shí)我們是以單線程方式調(diào)用該類的方法,也不得不承受塊同步帶來的額外開銷。Java在1.5中提供了非同步版本的StringBuilder類,這樣在單線程應(yīng)用中可以消除因同步而帶來的額外開銷,對(duì)于多線程程序,可以繼續(xù)選擇StringBuffer,或者在自己認(rèn)為需要同步的代碼部分加同步塊。
?? ?
六十八、executor和task優(yōu)先于線程:

?? ?? 在Java 1.5 中提供了java.util.concurrent包,在這個(gè)包中包含了Executor Framework框架,這是一個(gè)很靈活的基于接口的任務(wù)執(zhí)行工具。該框架提供了非常方便的調(diào)用方式和強(qiáng)大的功能,如:
?? ?? ExecutorService executor = Executors.newSingleThreadExecutor();? //創(chuàng)建一個(gè)單線程執(zhí)行器對(duì)象。
?? ?? executor.execute(runnable);? //提交一個(gè)待執(zhí)行的任務(wù)。
?? ?? executor.shutdown();? //使執(zhí)行器優(yōu)雅的終止。
?? ?? 事實(shí)上,Executors對(duì)象還提供了更多的工廠方法,如適用于小型服務(wù)器的Executors.newCachedThreadPool()工廠方法,該方法創(chuàng)建的執(zhí)行器實(shí)現(xiàn)類對(duì)于小型服務(wù)器來說還是比較有優(yōu)勢的,因?yàn)樵谄鋬?nèi)部實(shí)現(xiàn)中并沒有提供任務(wù)隊(duì)列,而是直接將任務(wù)提交給當(dāng)前可用的線程,如果此時(shí)沒有可用的線程了,則創(chuàng)建一個(gè)新線程來執(zhí)行該任務(wù)。因此在任務(wù)數(shù)量較多的大型服務(wù)器上,由于該機(jī)制創(chuàng)建了大量的工作者線程,這將會(huì)導(dǎo)致系統(tǒng)的整體運(yùn)行效率下降。對(duì)于該種情況,Executors提供了另外一個(gè)工廠方法Executors.newFixedThreadPool(),該方法創(chuàng)建的執(zhí)行器實(shí)現(xiàn)類的內(nèi)部提供了任務(wù)隊(duì)列,用于任務(wù)緩沖。
?? ?? 相比于java.util.Timer,該框架也提供了一個(gè)更為高效的執(zhí)行器實(shí)現(xiàn)類,通過工廠方法Executors.ScheduledThreadPool()可以創(chuàng)建該類。它提供了更多的內(nèi)部執(zhí)行線程,這樣在執(zhí)行耗時(shí)任務(wù)是,其定時(shí)精度要優(yōu)于Timer類。


六十九、并發(fā)工具優(yōu)先于wait和notify:

?? ?? java.util.concurrent中更高級(jí)的工具分成三類:Executor Framework、并發(fā)集合(Concurrent Collection)以及同步器(Synchronizer)。相比于java.util中提供的集合類,java.util.concurrent中提供的并發(fā)集合就有更好的并發(fā)性,其性能通常數(shù)倍于普通集合,如ConcurrentHashMap等。換句話說,除非有極其特殊的原因存在,否則在并發(fā)的情況下,一定要優(yōu)先選擇ConcurrentHashMap,而不是Collections.syschronizedmap或者Hashtable。
????? java.util.concurrent包中還提供了阻塞隊(duì)列,該隊(duì)列極大的簡化了生產(chǎn)者線程和消費(fèi)者線程模型的編碼工作。
? ? ? 對(duì)于同步器,concurrent包中給出了四種主要的同步器對(duì)象:CountDownLatch、Semaphore、CyclicBarrier和Exchanger。這里前兩種比較常用。在該條目中我們只是簡單介紹一個(gè)CountDownLatch的優(yōu)勢,該類允許一個(gè)或者多個(gè)線程等待一個(gè)或者多個(gè)線程來做某些事情。CountDownLatch的唯一構(gòu)造函數(shù)帶有一個(gè)int類型的參數(shù) ,這個(gè)int參數(shù)是指允許所有在等待的線程被處理之前,必須在鎖存器上調(diào)用countDown方法的次數(shù)。
?? ?? 現(xiàn)在我們給出一個(gè)簡單應(yīng)用場景,然后再給出用CountDownLatch實(shí)現(xiàn)該場景的實(shí)際代碼。場景描述如下:
? ? ? 假設(shè)想要構(gòu)建一個(gè)簡單的框架,用來給一個(gè)動(dòng)作的并發(fā)執(zhí)行定時(shí)。這個(gè)框架中包含單個(gè)方法,這個(gè)方法帶有一個(gè)執(zhí)行該動(dòng)作的executor,一個(gè)并發(fā)級(jí)別(表示要并發(fā)執(zhí)行該動(dòng)作的次數(shù)),以及表示該動(dòng)作的runnable。所有的工作線程自身都準(zhǔn)備好,要在timer線程啟動(dòng)時(shí)鐘之前運(yùn)行該動(dòng)作。當(dāng)最后一個(gè)工作線程準(zhǔn)備好運(yùn)行該動(dòng)作時(shí),timer線程就開始執(zhí)行,同時(shí)允許工作線程執(zhí)行該動(dòng)作。一旦最后一個(gè)工作線程執(zhí)行完該動(dòng)作,timer線程就立即停止計(jì)時(shí)。直接在wait和notify之上實(shí)現(xiàn)這個(gè)邏輯至少來說會(huì)很混亂,而在CountDownLatch之上實(shí)現(xiàn)則相當(dāng)簡單。見如下示例代碼:

1 ???? public static long time(Executor executor, int concurrency, final Runnable action) {
2 ???????? final CountDownLatch ready = new CountDownLatch(concurrency);
3 ???????? final CountDownLatch start = new CountDownLatch(1);
4 ???????? final CountDownLatch done = new CountDownLatch(concurrency);
5 ???????? for ( int i = 0; i < concurrency; i++) {
6 ???????????? executor.execute( new Runnable() {
7 ???????????????? public void run() {
8 ???????????????????? ready.countDown();
9 ???????????????????? try {
10 ???????????????????????? start.await();
11 ???????????????????????? action.run();
12 ???????????????????? } catch (InterruptedException e) {
13 ???????????????????????? Thread.currentThread().interrupt();
14 ???????????????????? } finally {
15 ???????????????????????? done.countDown();
16 ???????????????????? }
17 ???????????????? }
18 ???????????? });
19 ???????????? // 等待工作者線程準(zhǔn)備可以執(zhí)行,即所有的工作線程均調(diào)用ready.countDown()方法。
20 ???????????? ready.await();
21 ???????????? // 這里使用nanoTime,是因?yàn)槠渚_度高于System.currentTimeMills()。
22 ???????????? long startNanos = System.nanoTime();
23 ???????????? // 該語句執(zhí)行后,工作者線程中的start.await()均將被喚醒。
24 ???????????? start.countDown();
25 ???????????? // 下面的等待,只有在所有的工作者線程均調(diào)用done.countDown()之后才會(huì)被喚醒。
26 ???????????? done.await();
27 ???????????? return System.nanoTime() - startNanos;
28 ???????? }
29 ???? }
?


七十一、慎用延遲初始化:

?? ?? 延遲初始化作為一種性能優(yōu)化的技巧,它要求類的域成員在第一次訪問時(shí)才執(zhí)行必要的初始化動(dòng)作,而不是在類構(gòu)造的時(shí)候完成該域字段的初始化。和大多數(shù)優(yōu)化一樣,對(duì)于延遲初始化,最好的建議"除非絕對(duì)必要,否則就不要這么做"。延遲初始化如同一把雙刃劍,它確實(shí)降低了實(shí)例對(duì)象創(chuàng)建的開銷,卻增加了訪問被延遲初始化的域的開銷,這一點(diǎn)在多線程訪問該域時(shí)表現(xiàn)的更為明顯。見如下代碼:

1 ???? public class TestClass {
2 ???????? private final FieldType field;
3 ???????? synchronized FieldType getField() {
4 ???????????? if (field == null )
5 ???????????????? field = computeFieldValue();
6 ???????????? return field;
7 ???????? }
8 ???? }
?

????? 從上面的代碼可以看出,在每次訪問該域字段時(shí),均需要承擔(dān)同步的開銷。如果在真實(shí)的應(yīng)用中,在多線程環(huán)境下,我們確實(shí)需要為一個(gè)實(shí)例化開銷很大的對(duì)象實(shí)行延遲初始化,又該如何做呢?該條目提供了3中技巧:
? ? ? 1. 對(duì)于靜態(tài)域字段,可以考慮使用延遲初始化Holder class模式:

1 ???? public class TestClass {
2 ???????? private static class FieldHolder {
3 ???????????? static final FieldType field = computeFieldValue();
4 ???????? }
5 ???????? static FieldType getField() {
6 ???????????? return FieldHolder.field;
7 ???????? }
8 ???? }
?

?? ?? 當(dāng)getField()方法第一次被調(diào)用時(shí),它第一次讀取FieldHolder.field,導(dǎo)致FieldHolder類得到初始化。這種模式的魅力在于,getField方法沒有被同步,并且只執(zhí)行一個(gè)域訪問,因此延遲初始化實(shí)際上并沒有增加任何訪問成本。現(xiàn)在的VM將在初始化該類的時(shí)候,同步域的訪問。一旦這個(gè)類被初始化,VM將修補(bǔ)代碼,以便后續(xù)對(duì)該域的訪問不會(huì)導(dǎo)致任何測試或者同步。
? ? ? 2. 對(duì)于實(shí)例域字段,可使用雙重檢查模式:

1 ???? public class TestClass {
2 ???????? private volatile FieldType f;
3 ???????? FieldType getField() {
4 ???????????? FieldType result = f;
5 ???????????? if (result == null ) {
6 ???????????????? synchronized ( this ) {
7 ???????????????????? result = f;
8 ???????????????????? if (result == null )
9 ???????????????????????? f = result = computeFieldValue();
10 ???????????????? }
11 ???????????? }
12 ???????????? return result;
13 ???????? }
14 ???? }
?

????? 注意在上面的代碼中,首先將域字段f聲明為volatile變量,其語義在之前的條目中已經(jīng)給出解釋,這里將不再贅述。再者就是在進(jìn)入同步塊之前,先針對(duì)該字段進(jìn)行驗(yàn)證,如果不是null,即已經(jīng)初始化,就直接返回該域字段,從而避免了不必要的同步開銷。然而需要明確的是,在同步塊內(nèi)部的判斷極其重要,因?yàn)樵诘谝淮闻袛嘀蠛瓦M(jìn)入同步代碼塊之前存在一個(gè)時(shí)間窗口,而這一窗口則很有可能造成不同步的錯(cuò)誤發(fā)生,因此第二次驗(yàn)證才是決定性的。
? ? ? 在該示例代碼中,使用局部變量result代替volatile的域字段,可以避免在后面的訪問中每次都從主存中獲取數(shù)據(jù),從而提高函數(shù)的運(yùn)行性能。事實(shí)上,這只是一種代碼優(yōu)化的技巧而已。
?? ?? 針對(duì)該技巧,最后需要補(bǔ)充的是,在很多并發(fā)程序中,對(duì)某一狀態(tài)的測試,也可以使用該技巧。
?? ?? 3. 對(duì)于可以接受重復(fù)初始化實(shí)例域字段,可使用單重檢查模式:

1 ???? public class TestClass {
2 ???????? private volatile FieldType f;
3 ???????? FieldType getField() {
4 ???????????? FieldType result = f;
5 ???????????? if (result == null )
6 ???????????????? f = result = computeFieldValue();
7 ???????????? return result;
8 ???????? }
9 ???? }?

Effective Java (并發(fā))


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論