前言:只有光頭才能讓人變強
多線程知識導圖:
多線程大家在初學的時候,對這個知識點應該有不少的疑惑的。我認為主要原因有兩個:
- 多線程在初學的時候不太好學,並且一般寫項目的時候也很少用得上(至少在初學階段時寫的項目基本不需要自己創建線程)。
- 多線程的知識點在面試經常考,多線程所涉及的知識點非常多,難度也不低。
這就會給人帶來一種感覺「這破玩意涉及的東西是真的廣,平時也不怎麼用,怎麼面試就偏偏愛問這個鬼東西」
不多BB,我要開始了。
為什麼使用多線程?
首先,我們要明確的是「為什麼要使用多線程」,可能有人會認為「使用多線程就是為了加快程序運行的速度啊」。如果你是這樣回答了,那面試官可能會問你「那多線程是怎麼加快程序運行速度的?」
於我的理解:使用多線程最主要的原因是提高系統的資源利用率。
現在CPU基本都是多核的,如果你只用單線程,那就是隻用到了一個核心,其他的核心就相當於空閒在那裡了。
廁所的坑位有5個,如果只用一個坑位,那不是很虧?比如現在我有5個人要上廁所。
在單線程的時候:進去一個人解決要10分鐘,然後後面的人都得等一個坑位。那總的時間就要花費50分鐘。
在多線程的時候,進去一個人要解決10分鐘,然後後面的人發現還有別的坑位,就去別的坑位了,不是傻瓜地等一個坑位。
我們可以把「等坑位」看作是IO操作,眾所周知IO操作相對於CPU而言是非常慢的,CPU等待IO那段時間是空閒的。如果我們需要做類似IO這種慢的操作,可以開多個線程出來,儘量不要讓CPU空閒下來,提高系統的資源利用率。
說白了,我們就是在**「壓榨」**CPU的資源。本來就有的資源,如果有需要,我們就應當好好利用。
多線程不是銀彈,並不是說線程越多,我們的資源利用效率就越好。執行IO操作我們線程可以適當多一點,因為很多時候CPU是相對空閒的。如果是計算型的操作,本來CPU就不空閒了,還開很多的線程就不對了(有多線程就會有線程切換的問題,線程切換都是需要耗費資源的)
多線程離我們遠嗎?
多線程其實離我們很近,只是很多時候我們感知不到它的存在而已。
Tomcat我相信每個Java後端的同學都認識它,它就是以多線程去響應請求的,我們可以在server.xml中配置連接池的配置,比如:
<code>/<code>
Tomcat處理每一個請求都會從線程連接池裡邊用一個線程去處理,這顯然是多線程的操作。然後這個請求線程順藤摸瓜到了我們的Servlet,執行對應的service()方法。
而我們的service方法是無狀態的,多個線程請求service方法,往往都沒有操作共享變量,不操作共享變量就不會有線程安全問題。
上面只是用了Servlet舉例,我們常用的SpringMVC其實也是一樣的(畢竟底層還是Servlet)。
還有我們在連接數據庫的時候,也會用對應的連接池(Druid、C3P0、DBCP等),比如常見的Druid配置:
<code> /<code>
我想說的是:我們日常開發的程序幾乎都是多線程模式的,只是絕大多數時候我們沒感知到而已。很多時候都是框架幫我們屏蔽掉了。
多線程知識重要嗎?
從上面總結下來,我們可以發現:我們日常「關於多線程的代碼」寫得不多,但是我們寫的程序代碼的的確確是在多線程的環境下跑的。
如果我們不懂多線程知識,很直接的一個現實:
從文章最開頭的思維導圖,我們可以發現多線程的知識點還是很多的,我們起碼得知道:
- 線程和進程的區別
- Thead類的常見方法
- 可以用什麼手段來解決線程安全性問題
- Synchronized和Lock鎖的區別
- 什麼是AQS、ReentrantLock和ReentrantReadWriteLock鎖
- JDK自帶的線程池有哪幾個,線程池的構造方法重要的參數
- 什麼是死鎖,怎麼避免死鎖
- CountDownLatch、CyclicBarrier、Semaphore是什麼?
- Atomic包下的常見子類,什麼是CAS,CAS會有什麼問題
- ThreadLocal是什麼?
- .....//
雖然在工作中未必會全部用得上,但如果項目真的用到了,我們如果學過了可能就可以很快地理解當時為什麼要這樣設計(我覺得去挖掘過程還是挺有意思的)。
「我可能不用,但你必須要有」
這個道理也很容易懂:「我買電腦的時候,雖然我是木耳聽不出什麼音質出來,但你音質就是得好」。企業招人的時候也一樣「你在工作的時候未必要寫,但你必須要會」
至少在我看來,從求職的角度出發,多線程是很重要的。之前我還整理過在我當時校招經常被問到的多線程面試題目:
- 多線程瞭解多少啊?使用多線程會有什麼問題?你是怎麼理解“線程安全”的?
- 如果我現在想要某個操作等待線程結束之後才執行,有什麼方法可以實現?為什麼要用CountDownLatch?CountDownLatch的底層是什麼?(引出AQS)
- synchronized關鍵字來說一下,它的用途是什麼?synchronized底層的原理是什麼?
- 線程安全的容器有哪些?(著重於ConcurrentHashMap、CopyWriteOnArrayList與其他非線程安全容器的區別以及它們的具體實現)
- ThreadLocal你瞭解過嗎?主要是用來幹什麼的?具體的源碼實現原理來說一下吧
- 產生死鎖的條件是什麼?我們可以如何避免死鎖?(可延伸到操作系統層面上的死鎖)
- synchronized鎖和ReentrantLock鎖有什麼區別呀?
- 線程池你應該也看過吧,來說說為什麼要用線程池。JDK默認實現了幾個線程池,分別有xxx(自然地ThreadPoolExecutor構造函數的常用幾個參數你也得一起說出來)
- ...
我在工作中用到的線程知識有哪些
本來是打算這篇文章主旋律就寫這塊的,然後我翻了一下自己維護的系統,用到的線程的地方還真的不是很多...
我就拿我現在的系統用到線程相關知識的幾個例子吧。
線程池
我這邊有個調度系統,運營設置了對應的時間,該任務就去執行,執行的內容大致就是去讀HDFS文件,然後將數據組裝,再傳遞到下游。
任務觸發了以後,我們直接將這個任務交給一個線程池去處理,交由線程池後就直接返回SUCCESS。
這樣做的好處是什麼?如果多個任務同時觸發,那可能某些任務執行時間過長,請求可能會被阻塞住,而我們如果放在線程池中可以提高系統的吞吐量。
使用線程池的時候,往往我們的調用方都不需要考慮請求是否立馬處理成功。假設線程池在處理任務的時候因為某些原因失敗了,我們可以走報警機制(用郵件/短信等渠道去提醒請求方即可)。
不知道大家學過消息隊列了沒有,我們常常說消息隊列是異步的,很多時候調用方的請求我們丟到消息隊列裡邊,就告訴調用方我們這條請求處理成功了。實際上,這個請求可能還交由下游的多個系統去處理,下游的系統可能也是異步的.....
在使用線程池的時候,很多時候我們也是把他當做異步來使(WebFlux實際上也是將請求丟到線程池嘛),只要我們的系統之間交互不是強一致性的,又希望提高系統的吞吐量,我們就可以考慮使用線程池。
輪詢
有的時候,我們需要有一個線程去輪詢處理某些任務。
比如,我的系統會有發短信的功能,我調用渠道商的下發接口的後,我需要拿到短信的回執信息,於是我就需要去調用渠道商的回執接口。
此時最簡單的做法就是開一個線程,不斷的輪詢渠道商的回執接口(我們設定輪詢的間隔時間即可)
<code>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();/<code>
或者有的時候,我們把任務放到內存阻塞隊列或者Redis,也是通過一個線程輪詢去取「隊列」的數據。
藉助juc包實現線程安全
juc其實就是java.util.concurrent包
我們在使用線程的時候,或者在日常開發的時候,都是得考慮我們現在使用的場景是否是線程安全的。
如果不是線程安全的,我們可以做什麼東西來使我們的程序變得線程安全。
- 如果是集合,我們可以考慮一下juc包下的集合類。
- 如果是數值/對象,我們可以考慮一下atomic包下的類。
- 如果是涉及到線程的重複利用,我們可以考慮一下是否要用線程池。
- 如果涉及到對線程的控制(比如一次能使用多少個線程,當前線程觸發的條件是否依賴其他線程的結果),我們可以考慮CountDownLatch/Semaphore等等
- 如果synchronized無法滿足你,我們可以考慮lock包下的類
乾貨
為大家整理了一些乾貨內容,思維導圖,面試專題。內容非常非常豪橫,有想法的朋友可以瀏覽一波。
領取方式:關注並轉發,後臺私信我{資料}即可白嫖。
作者:Java3y
鏈接:https://juejin.im/post/5e8bd541518825736b74912c
來源:掘金