<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 04 人多力量未必大—并發可能會遇到的問題 > 不安于小成,然后足以成大器;不誘于小利,然后可以立遠功。 > ——方孝孺 專欄已經寫到第三篇,但我們貌似還沒體驗到多線程帶來的好處,反倒是惹出了一些麻煩。上一篇專欄中,我們試圖采用多線程并發,加快抄寫單詞的速度,但是事與愿違,不但沒有提升,還做了無用功。最糟糕的是程序執行結果是錯誤的。人多雖然力量大,但也會有各種問題和麻煩。 ![圖片描述](https://img.mukewang.com/5d75b1ed0001df1a10960580.jpg) 如上圖,如果問題處理得不得當,人越多反而會越亂。上一節并發抄寫單詞程序的問題不知大家思考的如何。其實很簡單,問題出在共享資源的訪問上。 ## 并發抄寫單詞問題分析 回到抄單詞這個問題上,我們試圖引入更多的學生來一塊完成任務,那么這些學生怎么知道目前抄寫多少單詞了?自己是否還需要繼續抄寫呢?我們看相關代碼: ~~~java if (punishment.getLeftCopyCount() > 0) { int leftCopyCount = punishment.getLeftCopyCount(); System.out.println(threadName+"線程-"+name + "抄寫" + punishment.getWordToCopy() + "。還要抄寫" + --leftCopyCount + "次"); punishment.setLeftCopyCount(leftCopyCount); } ~~~ 我用通俗的方式來說明這段代碼的邏輯。為了讓參與抄寫單詞的學生知道剩余抄寫的數量,我們找來了一塊小黑板,然后把剩余的總量寫在上面,每個學生抄寫之前先看一眼黑板,如果剩余的數量大于零,那么還需要繼續抄寫,抄寫完后,擦掉黑板上的數字,把剩余數量-1,寫上去。 **流程圖如下:** ![圖片描述](https://img.mukewang.com/5d723a1a0001337407870415.jpg)OK,一個人按照這個流程抄寫是沒問題的,但是多個人同時抄寫,問題就多了。 1. 讀取次數和抄完更新次數之間有時間間隔,此時別的學生也會讀到同樣的剩余次數,那么這次抄寫就是多余的; 2. 在更新leftCopyCount的時候,可能其它多個線程已經更新過了,也就是說此時leftCopyCount并不是你當初取出來的值,那么可能會把剩余數量更新的比此時還要大。這樣其它線程的抄寫就白做了。因為剩余數量被更新了回去。 ## 嘗試解決并發問題 為了解決這兩個問題我們修改copyWord方法代碼如下: ~~~java public void copyWord() { int count = 0; String threadName = Thread.currentThread().getName(); while (true) { if (punishment.getLeftCopyCount() > 0) { int leftCopyCount = punishment.getLeftCopyCount(); leftCopyCount--; if(leftCopyCount<punishment.getLeftCopyCount()){ punishment.setLeftCopyCount(leftCopyCount); } System.out.println(threadName+"線程-"+name + "抄寫" + punishment.getWordToCopy() + "。還要抄寫" + leftCopyCount + "次"); count++; } else { break; } } System.out.println(threadName+"線程-"+name + "一共抄寫了" + count + "次!"); } ~~~ 可以看到代碼中主要有兩個變化: 1. 取得剩余次數后馬上更新-1后的次數。看似是避免了讀取和更新間的時間間隔。 2. 更新剩余次數前先判斷自己的更新次數是否為最新,避免更新后次數反而變大的問題。 這么修改后看起來好像沒有問題了,那么我們再來試一下。 執行以下main方法: ~~~java public static void main(String[] args) { Punishment punishment = new Punishment(100,"internationalization"); Student xiaoming = new Student("小明",punishment); xiaoming.start(); Student xiaozhang = new Student("小張",punishment); xiaozhang.start(); Student xiaozhao = new Student("小趙",punishment) xiaozhao.start(); } ~~~ 我們啟動三個線程并發抄寫。可以在輸出中找到如下關鍵信息: ~~~ 小趙線程-小趙一共抄寫了48次! 小明線程-小明一共抄寫了25次! 小張線程-小張一共抄寫了27次! ~~~ 總數是100,問題解決了!等等,真的解決了嗎?我們回過頭再看改后的copyWord代碼,雖然程序讀取剩余次數后,馬上更新,并且加了小于才更新的判斷。但是仔細想想,這樣并不是萬全之策,因為小明和小張很可能恰巧同時去看剩余次數,取得剩余次數n后,各自計算剩余次數為n-1,但是假如小明正好計算的快一點,小明先把剩余次數更新為了n-1,雖然小張不符合更新條件,但是在剩余第n次的這次抄寫上,小明和小張各抄寫了一次,也就是說多抄寫了一次。 ## 線程安全 為什么上面代碼打出的日志中,三人抄寫總和是正確的100呢?有沒有可能是抄寫數量太小,全部抄寫完也沒有發生上面描述的兩人同時去查看剩余次數的情況?為了驗證這個推論,我們把抄寫次數增多,看是否會出現問題。 在我的電腦上,抄寫數量加大到1000,三人抄寫總和依然是正確的。但加大到10000時,問題出現了,有時會出現三人抄寫次數大于10000的現象。我繼續加大到1000000,此時基本每次執行,三人執行總和都要超出1-5次。以上實驗結果,根據實驗電腦的不同會有所區別。現在已經能夠得出結論了,這樣修改是不行的,原因前文已經說明,因為有小概率兩人甚至三人同時查看剩余次數,導致重復抄寫。 以上所描述的問題,就是大家耳熟能詳的線程安全問題。線程安全問題來源于并發時對共享資源的操作。在本例中,我們把剩余次數寫在黑板上,大家都去黑板上讀取剩余次數并更新。那么共享資源就是黑板上的剩余抄寫次數。 我們先不談代碼如何修改,我們來想一想現實生活中如何解決上述問題。 問題出在小明讀取剩余次數的同時,小張、小趙也可以讀取,三人很可能讀到同樣的次數。并且讀取完,三人都會根據自己的計算去更新剩余次數,所以才會亂了套。我們可以改為誰要讀取次數時先做個標記(比如在次數邊上寫上自己名字),代表自己在操作,此時別人只能等待。詳細流程如下圖: ![圖片描述](https://img.mukewang.com/5d7239d100013ecd10820435.jpg)1、讀取剩余次數前,先看紙上是否有名字。沒有名字,在紙上寫上自己的名字; 2、如果紙上已經有名字則等待,并且一直觀察紙上名字是否被擦除; 3、成功寫上自己名字的同學,更新次數為n-1; 4、擦掉自己的名字; 5、其他等待者觀察到名字被擦掉,則搶著寫上自己的名字。 這樣確保了同一時間只有一個人在操作剩余次數,再也不會亂套了。 ## 其它多線程相關概念 以上流程引入了多線程中的一個重要概念–**同步**。所謂的同步就是某一段流程同時只能有一個線程執行,其它線程需要等待。對于本例,讀取剩余次數,并更新剩余次數這兩步操作需要做同步控制。操作剩余次數之前需要寫名字代表自己在做操作,這是在**加鎖**。而擦除名字則是**釋放鎖**。假如小明先成功寫上自己的名字,而小張和小趙按照先來后到的順序排隊,那么就是**公平鎖**。但假如兩人并不排隊,而是通過爭搶獲取寫名字的權利,那么這就是**非公平鎖**。在這種情況下,如果小張很瘦弱,既搶不過小趙,也搶不過小明,那么小張永遠無法讀取剩余次數,也就無法抄寫單詞,這種情況就叫做**線程餓死**。 線程安全問題通過加鎖可以得到解決。Java也提供了特定場景下更為輕量級的解決方法。后面的文章會有更為詳盡的描述。本篇文章只是拋出了多線程開發中,可能存在的問題,及問題產生的原因。上文還通過例子引申出多個多線程中的概念,相信結合例子都很容易理解,后面的文章還會反復提及并有專門的詳解。 ## 總結 多線程開發,復雜就復雜在處理線程安全問題上。如果代碼寫得不好,就會有各種同步相關的問題產生,而且很難調試。不過好在我們有很多種方式能夠解決。后面的文章會逐一講解。 專欄寫到這里,其實我們學習多線程的兩個主要目標已經清楚: 1. 如何實現多線程; 2. 如何解決線程安全問題。 后面的文章都會圍繞這兩個問題進行講解。我們會先學習如何實現多線程,再看如何解決多線程中的問題。難點在第2點上。要想知道如何解線程安全問題,就要深刻理解問題產生的根本原因是什么。另外要深刻理解解決問題的原理,而不僅僅是記住解決方法。這樣才能做到一通百通,遇到問題靈活應對。其實我們學習每一樣技術都是同樣的道理,千萬不要只停留在會用層面,否則遇到問題只會從表面現象入手,但n種表面現象可能是同一個根本原因。如果我們了解原理,遇到再多問題也無所畏懼。因此,本專欄絕不會停留在使用層面,而是會深入到底層原理。前三篇只算是個開胃菜。目的讓讀者了解多線程的概念及簡單實現,同時知曉多線程存在的問題。后面的文章難度會越來越大,不過不用擔心,只要跟著專欄認真學習下來,你一定能輕松掌握。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看