并發(fā)的挑戰(zhàn)

并發(fā)挑戰(zhàn)

上下文切換

即使是單核處理器也支持多線程執(zhí)行代碼,CPU通過給每個線程分配CPU時間片來實現(xiàn) 這個機(jī)制。時間片是CPU分配給各個線程的時間,因為時間片非常短,所以CPU通過不停地切 換線程執(zhí)行,讓我們感覺多個線程是同時執(zhí)行的,時間片一般是幾十毫秒(ms)。
CPU通過時間片分配算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個時間片后會切換到下一個 任務(wù)。但是,在切換前會保存上一個任務(wù)的狀態(tài),以便下次切換回這個任務(wù)時,可以再加載這 個任務(wù)的狀態(tài)。所以任務(wù)從保存到再加載的過程就是一次上下文切換。
多線程不一定快,因為他有線程切換的損耗。

如何減少上下文切換

減少上下文切換的方法有無鎖并發(fā)編程、CAS算法、使用最少線程和使用協(xié)程。

  • 無鎖并發(fā)編程。多線程競爭鎖時,會引起上下文切換,所以多線程處理數(shù)據(jù)時,可以用一 些辦法來避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
  • CAS算法。Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖。 ·使用最少線程。避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來處理,這樣會造成大量線程都處于等待狀態(tài)。
  • 協(xié)程:在單線程里實現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個任務(wù)間的切換。

減少上下文切換實戰(zhàn)

通過減少線上大量WAITING的線程,來減少上下文切換次數(shù)。
第一步:用jstack命令dump線程信息,看看pid為3117的進(jìn)程里的線程都在做什么。

sudo -u admin /opt/ifeve/java/bin/jstack 31177 > /home/tengfei.fangtf/dump17

第二步:統(tǒng)計所有線程分別處于什么狀態(tài),發(fā)現(xiàn)300多個線程處于WAITING(onobject- monitor)狀態(tài)。

  [tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
           | sort | uniq -c
    39 RUNNABLE
    21 TIMED_WAITING(onobjectmonitor)
    6 TIMED_WAITING(parking)
    51 TIMED_WAITING(sleeping)
    305 WAITING(onobjectmonitor)
    3 WAITING(parking)

第三步:打開dump文件查看處于WAITING(onobjectmonitor)的線程在做什么。發(fā)現(xiàn)這些線 程基本全是JBOSS的工作線程,在await。說明JBOSS線程池里線程接收到的任務(wù)太少,大量線 程都閑著。

   "http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in
       Object.wait() [0x0000000052423000]
    java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
    at java.lang.Object.wait(Object.java:485)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1464)
    - locked <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1489)
    at java.lang.Thread.run(Thread.java:662)

第四步:減少JBOSS的工作線程數(shù),找到JBOSS的線程池配置信息,將maxThreads降到 100。

   <maxThreads="250" maxHttpHeaderSize="8192"
    emptySessionPath="false" minSpareThreads="40" maxSpareThreads="75"
        maxPostSize="512000" protocol="HTTP/1.1"
    enableLookups="false" redirectPort="8443" acceptCount="200" bufferSize="16384"
    connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI= "true">

第五步:重啟JBOSS,再dump線程信息,然后統(tǒng)計WAITING(onobjectmonitor)的線程,發(fā)現(xiàn) 減少了175個。WAITING的線程少了,系統(tǒng)上下文切換的次數(shù)就會少,因為每一次從 WAITTING到RUNNABLE都會進(jìn)行一次上下文的切換。

[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
       | sort | uniq -c
      44 RUNNABLE
      22 TIMED_WAITING(onobjectmonitor)
      9 TIMED_WAITING(parking)
      36 TIMED_WAITING(sleeping)
      130 WAITING(onobjectmonitor)
   1  WAITING(parking)

死鎖

鎖是個非常有用的工具,運(yùn)用場景非常多,因為它使用起來非常簡單,而且易于理解。但 同時它也會帶來一些困擾,那就是可能會引起死鎖,一旦產(chǎn)生死鎖,就會造成系統(tǒng)功能不可 用。讓我們先來看一段代碼,這段代碼會引起死鎖,使線程t1和線程t2互相等待對方釋放鎖。

import java.lang.*;

public class DeadLockDemo {

    private static String A = "A";
    private static String B = "B";

    public static void main(String[] args) {
        new DeadLockDemo().deadLock();
    }

    private void deadLock() {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A) {
                    try {

                        Thread.currentThread().sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("1");
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    synchronized (A) {
                        System.out.println("2");
                    }
                }

            }
        });
        t1.start();
        t2.start();
    }
}

這段代碼只是演示死鎖的場景,在現(xiàn)實中你可能不會寫出這樣的代碼。但是,在一些更為 復(fù)雜的場景中,你可能會遇到這樣的問題,比如t1拿到鎖之后,因為一些異常情況沒有釋放鎖 (死循環(huán))。又或者是t1拿到一個數(shù)據(jù)庫鎖,釋放鎖的時候拋出了異常,沒釋放掉。
一旦出現(xiàn)死鎖,業(yè)務(wù)是可感知的,因為不能繼續(xù)提供服務(wù)了,那么只能通過dump線程查看 到底是哪個線程出現(xiàn)了問題,以下線程信息告訴我們是DeadLockDemo類的第42行和第31行引 起的死鎖。


   "Thread-2" prio=5 tid=7fc0458d1000 nid=0x116c1c000 waiting for monitor entry [116c1b00
       java.lang.Thread.State: BLOCKED (on object monitor)
           at com.ifeve.book.forkjoin.DeadLockDemo$2.run(DeadLockDemo.java:42)
           - waiting to lock <7fb2f3ec0> (a java.lang.String)
           - locked <7fb2f3ef8> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:695)
   "Thread-1" prio=5 tid=7fc0430f6800 nid=0x116b19000 waiting for monitor entry [116b1800
       java.lang.Thread.State: BLOCKED (on object monitor)
           at com.ifeve.book.forkjoin.DeadLockDemo$1.run(DeadLockDemo.java:31)
           - waiting to lock <7fb2f3ef8> (a java.lang.String)
           - locked <7fb2f3ec0> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:695)

避免產(chǎn)生死鎖的幾個常見的方法:

  • 避免一個線程同時獲取多個鎖。
  • 避免一個線程在鎖內(nèi)同時占用多個資源,盡量保證每個鎖只占用一個資源。
  • 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機(jī)制。
  • 對于數(shù)據(jù)庫鎖,加鎖和解鎖必須在一個數(shù)據(jù)庫連接里,否則會出現(xiàn)解鎖失敗的情況。

資源限制的挑戰(zhàn)

什么是資源限制

資源限制是指在進(jìn)行并發(fā)編程時,程序的執(zhí)行速度受限于計算機(jī)硬件資源或軟件資源。硬件資源限 制有帶寬的上傳/下載速度、硬盤讀寫速度和CPU的處理速度。軟件資源限制有數(shù)據(jù)庫的連接 數(shù)和socket連接數(shù)等。

資源限制引發(fā)的問題

在并發(fā)編程中,將代碼執(zhí)行速度加快的原則是將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行, 但是如果將某段串行的代碼并發(fā)執(zhí)行,因為受限于資源,仍然在串行執(zhí)行,這時候程序不僅不 會加快執(zhí)行,反而會更慢,因為增加了上下文切換和資源調(diào)度的時間。

如何解決資源限制的問題

對于硬件資源限制,可以考慮使用集群并行執(zhí)行程序。既然單機(jī)的資源有限制,那么就讓 程序在多機(jī)上運(yùn)行。
對于軟件資源限制,可以考慮使用資源池將資源復(fù)用。比如使用連接池將數(shù)據(jù)庫和Socket 連接復(fù)用,或者在調(diào)用對方webservice接口獲取數(shù)據(jù)時,只建立一個連接。

在資源限制情況下進(jìn)行并發(fā)編程

如何在資源限制的情況下,讓程序執(zhí)行得更快呢?方法就是,根據(jù)不同的資源限制調(diào)整 程序的并發(fā)度,比如下載文件程序依賴于兩個資源——帶寬和硬盤讀寫速度。有數(shù)據(jù)庫操作 時,涉及數(shù)據(jù)庫連接數(shù),如果SQL語句執(zhí)行非??欤€程的數(shù)量比數(shù)據(jù)庫連接數(shù)大很多,則 某些線程會被阻塞,等待數(shù)據(jù)庫連接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容