Java开发中遇到具有挑战的事_Java并发编程的挑战:遇到的问题及如何解决
并發編程的目的是為了讓程序運行得更快,但是,并不是啟動更多的線程就能讓程序最大限度地并發執行。在進行并發編程時,如果希望通過多線程執行任務讓程序運行得更快,會面臨非常多的挑戰,比如上下文切換的問題、死鎖的問題,以及受限于硬件和軟件的資源限制問題,本章會介紹幾種并發編程的挑戰以及解決方案。
上下文切換
即使是單核處理器也支持多線程執行代碼, CPU通過給每個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,因為時間片非常短,所以ICPU通過不停地切換線程執行,讓我們感覺多個線程是同時執行的時間片一般是幾十毫秒(ms)。
CPU通過時間片分配算法來循環執行任務,當前任務執行-一個時間片后會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再加載這個任務的狀態。所以任務從保存到再加載的過程就是一次上下文切換。
這就像我們同時讀兩本書,當我們在讀一本英文的技術書時,發現某個單詞不認識,于是便打開中英文字典,但是在放下英文技術書之前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞之后,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多線程的執行速度。
多線程一定快嗎
下面的代碼演示串行和并發執行并累加操作的時間,請分析:下面的代碼并發執行一定比串行執行快嗎?
上述問題的答案是“不一定”,測試結果如表所示。
從表中可以發現,當并發執行累加操作不超過百萬次時,速度會比串行執行累加操作要慢。那么,為什么并發執行的速度會比串行慢呢?這是因為線程有創建和上下文切換的開銷。
測試上下文切換次數和時長
下面我們來看看有什么工具可以度量上下文切換帶來的消耗。
使用I mbench31I可以測量上下文切換的時長。使用vmstat可以測量上下文切換的次數。Imbench3是一個性能分析工具。.
下面是利用vmstat測量上下文切換次數的示例。
CS(Content Switch)表示上下文切換的次數,從上面的測試結果中我們可以看到,上下文每1秒切換1000多次。
如何減少,上下文切換
減少上下文切換的方法有無鎖并發編程、CAS算法、使用最少線程和使用協程。
無鎖并發編程。多線程競爭鎖時,會引起上下文切換,所以多線程處理數據時,可以用一些辦法來避免使用鎖,如將數據的ID按照Hash算法取模分段,不同的線程處理不同段的數據。
CAS算法:Java的Atomic包使用CAS算法來更新數據,而不需要加鎖。使用最少線程:避免創建不需要的線程.比如任務很少,但是創建了很多線程來處理,這樣會造成大量線程都處于等待狀態。協程:在單線程里實現多任務的調度,并在單線程里維持多個任務間的切換。
減少上下文切換實戰
本節將通過減少線上大量WAITING的線程,來減少上下文切換次數。
第一步:用jstack命令dump線程信息,看看pid為3117的進程里的線程都在做什么。
第二步:統計所有線程分別處于什么狀態,發現300多個線程處于WAITING(onobject-monitor)狀態。
第三步:打開dump文件查看處于WAITING(onobjectmonitor)的線程在做什么。發現這些線程基本全是JBOSS的工作線程,在await。說明JBOSS線程池里線程接收到的任務太少,大量線程都閑著。
第四步:減少JBOSS的工作線程數,找到JBOSS的線程池配置信息,將maxThreads降到100。
第五步:重啟JBOSS,再dump線程信息,然后統計WAITING(onobjectmonitor)的線程,發現減少了175個。WAITING的線程少了,系統上下文切換的次數就會少,因為每次從WATTTING到RUNNABLE都會進行一 次上下文的切換。讀者也可以使用vmstat命令測試一 下。
死鎖
鎖是個非常有用的工具,運用場景非常多,因為它使用起來非常簡單,而且易于理解。但同時它也會帶來-些困擾,那就是可能會引起死鎖,一旦產生死鎖,就會造成系統功能不可用。讓我們先來看一段代碼,這段代碼會引起死鎖。使線程t1和線程2互相等待對方釋放鎖。
這段代碼只是演示死鎖的場景,在現實中你可能不會寫出這樣的代碼。但是,在一些更為復雜的場景中,你可能會遇到這樣的問題,比如t1拿到鎖之后,因為一些異常情況沒有釋放鎖(死循環)。又或者是t1拿到一個數據庫鎖,釋放鎖的時候拋出了異常,沒釋放掉。
一旦出現死鎖,業務是可感知的,因為不能繼續提供服務了,那么只能通過dump線程查看到底是哪個線程出現了問題,以下線程信息告訴我們是DeadI ockDemo類的第42行和第31行引起的死鎖。
現在我們介紹避免死鎖的幾個常見方法。
避免一個線程同時獲取多個鎖。避免一個線程在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源。嘗試使用定時鎖,使用lock.tryLock(imeout)來替代使用內部鎖機制。對于數據庫鎖,加鎖和解鎖必須在一個數據庫連接里,否則會出現解鎖失敗的情況。資源限制的挑戰
(1)什么是資源限制
資源限制是指在進行并發編程時,程序的執行速度受限于計算機硬件資源或軟件資源。例如,服務器的帶寬只有2Mb/s,某個資源的下載速度是1Ml/s每秒,系統啟動10個線程下載資源,下載速度不會變成10Mb/s,所以在進行并發編程時,要考慮這些資源的限制。硬件資源限制有帶寬的上傳/下載速度、硬盤讀寫速度和CPU的處理速度。軟件資源限制有數據庫的連接數和socket連接數等。
(2)資源限制引發的問題
在并發編程中,將代碼執行速度加快的原則是將代碼中串行執行的部分變成并發執行,但是如果將某段串行的代碼并發執行,因為受限于資源,仍然在串行執行,這時候程序不僅不會加快執行,反而會更慢,因為增加了上下文切換和資源調度的時間。例如,之前看到一段程序使用多線程在辦公網并發地下載和處理數據時,導致CPU利用率達到100%, 幾個小時都不能運行完成任務。后來修改成單線程,一個小時就執行完成了。
(3)如何解決資源限制的問題
對于硬件資源限制,可以考慮使用集群并行執行程序。既然單機的資源有限制,那么就讓程序在多機上運行。比如使用ODPS、Hadoop或者自己搭建服務器集群,不同的機器處理不同的數據。可以通過“數據ID%機器數",計算得到一個機器編號,然后由對應編號的機器處理這筆數據。
如何在資源限制的情況下,讓程序執行得更快呢?方法就是,根據不同的資源限制調整程序的并發度,比如下載文件程序依賴于兩個資源帶寬和硬盤讀寫速度。有數據庫操作時,涉及數據庫連接數,如果SQL語句執行非常快,而線程的數量比數據庫連接數大很多,則某些線程會被阻塞,等待數據庫連接。
總結
以上介紹了在進行并發編程時,大家可能會遇到的幾個挑戰,并給出了一些解決建議。有的并發程序寫得不嚴謹,在并發下如果出現問題,定位起來會比較耗時和棘手。所以,對于Java開發工程師而言,筆者強烈建議多使用JDK并發包提供的并發容器和工具類來解決并發問題,因為這些類都已經通過了充分的測試和優化,均可解決了本章提到的幾個挑戰。
當然,這些只是Java并發編程的冰山一角,完整的知識我已經整理成了一份文檔。需要的轉發文章關注私信“文檔”獲得領取方式。
總結
以上是生活随笔為你收集整理的Java开发中遇到具有挑战的事_Java并发编程的挑战:遇到的问题及如何解决的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bison实例
- 下一篇: Java字符串简化_关于java查询语句