清明花了幾天總結了多執行緒的知識點

Java3y發表於2020-04-07

前言

只有光頭才能變強。

文字已收錄至我的GitHub精選文章,歡迎Stargithub.com/ZhongFuChen…

在上週總結了一篇「工作中常用到的Java集合類」,反響還不錯。這周來寫寫Java另一個重要的知識點:「多執行緒

清明花了幾天總結了多執行緒的知識點

多執行緒大家在初學的時候,對這個知識點應該有不少的疑惑的。我認為主要原因有兩個:

  • 多執行緒在初學的時候不太好學,並且一般寫專案的時候也很少用得上(至少在初學階段時寫的專案基本不需要自己建立執行緒)。
  • 多執行緒的知識點在面試經常考,多執行緒所涉及的知識點非常多,難度也不低。

這就會給人帶來一種感覺「這破玩意涉及的東西是真的廣,平時也不怎麼用,怎麼面試就偏偏愛問這個鬼東西

不多BB,我要開始了。

清明花了幾天總結了多執行緒的知識點

為什麼使用多執行緒?

首先,我們要明確的是「為什麼要使用多執行緒」,可能有人會認為「使用多執行緒就是為了加快程式執行的速度啊」。如果你是這樣回答了,那面試官可能會問你「那多執行緒是怎麼加快程式執行速度的?」

於我的理解:使用多執行緒最主要的原因是提高系統的資源利用率

現在CPU基本都是多核的,如果你只用單執行緒,那就是隻用到了一個核心,其他的核心就相當於空閒在那裡了。

廁所的坑位有5個,如果只用一個坑位,那不是很虧?比如現在我有5個人要上廁所。

在單執行緒的時候:進去一個人解決要10分鐘,然後後面的人都得等一個坑位。那總的時間就要花費50分鐘。

在多執行緒的時候,進去一個人要解決10分鐘,然後後面的人發現還有別的坑位,就去別的坑位了,不是傻瓜地等一個坑位。

清明花了幾天總結了多執行緒的知識點

我們可以把「等坑位」看作是IO操作,眾所周知IO操作相對於CPU而言是非常慢的,CPU等待IO那段時間是空閒的。如果我們需要做類似IO這種慢的操作,可以開多個執行緒出來,儘量不要讓CPU空閒下來,提高系統的資源利用率。

說白了,我們就是在**「壓榨」**CPU的資源。本來就有的資源,如果有需要,我們就應當好好利用。

多執行緒不是銀彈,並不是說執行緒越多,我們的資源利用效率就越好。執行IO操作我們執行緒可以適當多一點,因為很多時候CPU是相對空閒的。如果是計算型的操作,本來CPU就不空閒了,還開很多的執行緒就不對了(有多執行緒就會有執行緒切換的問題,執行緒切換都是需要耗費資源的)

清明花了幾天總結了多執行緒的知識點

多執行緒離我們遠嗎?

多執行緒其實離我們很近,只是很多時候我們感知不到它的存在而已。

Tomcat我相信每個Java後端的同學都認識它,它就是以多執行緒去響應請求的,我們可以在server.xml中配置連線池的配置,比如:

<Connector port="8080" maxThreads="350" maxHttpHeaderSize="8192" minSpareThreads="45" maxPostSize="512000" protocol="HTTP/1.1" enableLookups="false" redirectPort="8443" acceptCount="200" keepAliveTimeout="15000" maxKeepAliveRequests="-1" maxConnections="25000" connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI="true" URIEncoding="UTF-8" />
複製程式碼

Tomcat處理每一個請求都會從執行緒連線池裡邊用一個執行緒去處理,這顯然是多執行緒的操作。然後這個請求執行緒順藤摸瓜到了我們的Servlet,執行對應的service()方法。

清明花了幾天總結了多執行緒的知識點

而我們的service方法是無狀態的,多個執行緒請求service方法,往往都沒有操作共享變數,不操作共享變數就不會有執行緒安全問題。

清明花了幾天總結了多執行緒的知識點

上面只是用了Servlet舉例,我們常用的SpringMVC其實也是一樣的(畢竟底層還是Servlet)。

還有我們在連線資料庫的時候,也會用對應的連線池(Druid、C3P0、DBCP等),比如常見的Druid配置:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />

     <property name="filters" value="stat" />

     <property name="maxActive" value="20" />
     <property name="initialSize" value="1" />
     <property name="maxWait" value="60000" />
     <property name="minIdle" value="1" />

     <property name="timeBetweenEvictionRunsMillis" value="60000" />
     <property name="minEvictableIdleTimeMillis" value="300000" />

     <property name="testWhileIdle" value="true" />
     <property name="testOnBorrow" value="false" />
     <property name="testOnReturn" value="false" />

     <property name="poolPreparedStatements" value="true" />
     <property name="maxOpenPreparedStatements" value="20" />

     <property name="asyncInit" value="true" />
 </bean>
複製程式碼

我想說的是:我們日常開發的程式幾乎都是多執行緒模式的,只是絕大多數時候我們沒感知到而已。很多時候都是框架幫我們遮蔽掉了。

多執行緒知識重要嗎?

從上面總結下來,我們可以發現:我們日常「關於多執行緒的程式碼」寫得不多,但是我們寫的程式程式碼的的確確是在多執行緒的環境下跑的。

如果我們不懂多執行緒知識,很直接的一個現實:

生成結果

從文章最開頭的思維導圖,我們可以發現多執行緒的知識點還是很多的,我們起碼得知道:

  • 執行緒和程式的區別
  • Thead類的常見方法
  • 可以用什麼手段來解決執行緒安全性問題
  • Synchronized和Lock鎖的區別
  • 什麼是AQS、ReentrantLock和ReentrantReadWriteLock鎖
  • JDK自帶的執行緒池有哪幾個,執行緒池的構造方法重要的引數
  • 什麼是死鎖,怎麼避免死鎖
  • CountDownLatch、CyclicBarrier、Semaphore是什麼?
  • Atomic包下的常見子類,什麼是CAS,CAS會有什麼問題
  • ThreadLocal是什麼?
  • .....//

雖然在工作中未必會全部用得上,但如果專案真的用到了,我們如果學過了可能就可以很快地理解當時為什麼要這樣設計(我覺得去挖掘過程還是挺有意思的)。

我可能不用,但你必須要有

這個道理也很容易懂:「我買電腦的時候,雖然我是木耳聽不出什麼音質出來,但你音質就是得好」。企業招人的時候也一樣「你在工作的時候未必要寫,但你必須要會

至少在我看來,從求職的角度觸發,多執行緒是很重要的。之前我還整理過在我當時校招經常被問到的多執行緒面試題目:

  1. 多執行緒瞭解多少啊?使用多執行緒會有什麼問題?你是怎麼理解“執行緒安全”的?
  2. 如果我現在想要某個操作等待執行緒結束之後才執行,有什麼方法可以實現?為什麼要用CountDownLatch?CountDownLatch的底層是什麼?(引出AQS)
  3. synchronized關鍵字來說一下,它的用途是什麼?synchronized底層的原理是什麼?
  4. 執行緒安全的容器有哪些?(著重於ConcurrentHashMap、CopyWriteOnArrayList與其他非執行緒安全容器的區別以及它們的具體實現)
  5. ThreadLocal你瞭解過嗎?主要是用來幹什麼的?具體的原始碼實現原理來說一下吧
  6. 產生死鎖的條件是什麼?我們可以如何避免死鎖?(可延伸到作業系統層面上的死鎖)
  7. synchronized鎖和ReentrantLock鎖有什麼區別呀?
  8. 執行緒池你應該也看過吧,來說說為什麼要用執行緒池。JDK預設實現了幾個執行緒池,分別有xxx(自然地ThreadPoolExecutor建構函式的常用幾個引數你也得一起說出來)
  9. ...

我在工作中用到的執行緒知識有哪些

本來是打算這篇文章主旋律就寫這塊的,然後我翻了一下自己維護的系統,用到的執行緒的地方還真的不是很多...

我就拿我現在的系統用到執行緒相關知識的幾個例子吧。

執行緒池

我這邊有個排程系統,運營設定了對應的時間,該任務就去執行,執行的內容大致就是去讀HDFS檔案,然後將資料組裝,再傳遞到下游。

任務觸發了以後,我們直接將這個任務交給一個執行緒池去處理,交由執行緒池後就直接返回SUCCESS

清明花了幾天總結了多執行緒的知識點

這樣做的好處是什麼?如果多個任務同時觸發,那可能某些任務執行時間過長,請求可能會被阻塞住,而我們如果放線上程池中可以提高系統的吞吐量。

使用執行緒池的時候,往往我們的呼叫方都不需要考慮請求是否立馬處理成功。假設執行緒池在處理任務的時候因為某些原因失敗了,我們可以走報警機制(用郵件/簡訊等渠道去提醒請求方即可)。

不知道大家學過訊息佇列了沒有,我們常常說訊息佇列是非同步的,很多時候呼叫方的請求我們丟到訊息佇列裡邊,就告訴呼叫方我們這條請求處理成功了。實際上,這個請求可能還交由下游的多個系統去處理,下游的系統可能也是非同步的.....

在使用執行緒池的時候,很多時候我們也是把他當做非同步來使(WebFlux實際上也是將請求丟到執行緒池嘛),只要我們的系統之間互動不是強一致性的,又希望提高系統的吞吐量,我們就可以考慮使用執行緒池。

清明花了幾天總結了多執行緒的知識點

輪詢

有的時候,我們需要有一個執行緒去輪詢處理某些任務。

比如,我的系統會有發簡訊的功能,我呼叫渠道商的下發介面的後,我需要拿到簡訊的回執資訊,於是我就需要去呼叫渠道商的回執介面。

此時最簡單的做法就是開一個執行緒,不斷的輪詢渠道商的回執介面(我們設定輪詢的間隔時間即可)

Thread thread = new Thread(new Runnable() {
  @Override
  public void run() {
    while (true) {
      try {
        // 間隔一段時間輪詢一次                                           
        TimeUnit.MILLISECONDS.sleep(period);

        // 呼叫介面
        String result = http.post();

        // 得到result後進行處理(比如將結果插入到資料庫)
        smsDao.insert(result);
      }
    }
  });
thread.start();
複製程式碼

或者有的時候,我們把任務放到記憶體阻塞佇列或者Redis,也是通過一個執行緒輪詢去取「佇列」的資料。

清明花了幾天總結了多執行緒的知識點

藉助juc包實現執行緒安全

juc其實就是java.util.concurrent

清明花了幾天總結了多執行緒的知識點

我們在使用執行緒的時候,或者在日常開發的時候,都是得考慮我們現在使用的場景是否是執行緒安全的。

如果不是執行緒安全的,我們可以做什麼東西來使我們的程式變得執行緒安全。

  • 如果是集合,我們可以考慮一下juc包下的集合類。
  • 如果是數值/物件,我們可以考慮一下atomic包下的類。
  • 如果是涉及到執行緒的重複利用,我們可以考慮一下是否要用執行緒池。
  • 如果涉及到對執行緒的控制(比如一次能使用多少個執行緒,當前執行緒觸發的條件是否依賴其他執行緒的結果),我們可以考慮CountDownLatch/Semaphore等等
  • 如果synchronized無法滿足你,我們可以考慮lock包下的類

清明花了幾天總結了多執行緒的知識點

放乾貨

現在已經工作有一段時間了,為什麼還來寫多執行緒呢,原因有以下幾個:

  • 我是一個對排版有追求的人,如果早期關注我的同學可能會發現,我的GitHub、文章導航的read.me會經常更換。現在的GitHub導航也不合我心意了(太長了),並且早期的文章,說實話排版也不太行,我決定重新搞一波。
  • 我的文章會分發好幾個平臺,但文章發完了可能就沒人看了,並且圖床很可能因為平臺的防盜鏈就掛掉了。又因為有很多的讀者問我:”你能不能把你的文章轉成PDF啊?“
  • 我寫過很多系列級的文章,這些文章就幾乎不會有太大的改動了,就非常適合把它們給”持久化“。

基於上面的原因,我決定把我的系列文章彙總成一個PDF/HTML/WORD文件。說實話,打造這麼一個文件花了我不少的時間。為了防止白嫖,關注我的公眾號回覆「888」即可獲取。

PDF的內容非常非常長,乾貨非常非常的硬,有興趣的同學可以瀏覽一波。共有「129」頁

清明花了幾天總結了多執行緒的知識點

文件的內容均為手打,有任何的不懂都可以直接來問我(公眾號有我的聯絡方式)。

img

涵蓋Java後端所有知識點的開源專案(已有6 K star):github.com/ZhongFuChen…

如果大家想要實時關注我更新的文章以及分享的乾貨的話,微信搜尋Java3y

PDF文件的內容均為手打,有任何的不懂都可以直接來問我(公眾號有我的聯絡方式)。

清明花了幾天總結了多執行緒的知識點

清明花了幾天總結了多執行緒的知識點

清明花了幾天總結了多執行緒的知識點

清明花了幾天總結了多執行緒的知識點

相關文章