超詳細,理解這6個核心概念,輕鬆入門Java多線程!

前言 小編這裏整理了一份JAVA多線程併發編程的詳細思惟導圖,想了解的小夥伴能夠點開看看呢。 多線程相對於其餘 Java 知識點來說,有必定的學習門檻,而且瞭解起來比較費勁。在平時工做中如若使用不當會出現數據錯亂、執行效率低(還不如單線程去運行)或者死鎖程序掛掉等等問題,因此掌握瞭解多線程相當重要。 小編接下來會從基礎概念開始到最後的併發模型由淺入深,講解下線程方面的知識。 1、併發與並行 並行,表示兩個線程同時作事情。 併發,表示一會作這個事情,一會作另外一個事情,存在着調度。單核 CPU 不可能存在並行(微觀上)。 臨界區 臨界區用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。可是每一次,只能有一個線程使用它,一旦臨界區資源被佔用,其餘線程要想使用這個資源,就必須等待。 阻塞與非阻塞 阻塞和非阻塞一般用來形容多線程間的相互影響。好比一個線程佔用了臨界區資源,那麼其它全部須要這個資源的線程就必須在這個臨界區中進行等待,等待會致使線程掛起。這種狀況就是阻塞。 此時,若是佔用資源的線程一直不肯意釋放資源,那麼其它全部阻塞在這個臨界區上的線程都不能工做。阻塞是指線程在操做系統層面被掛起。阻塞通常性能很差,需大約8萬個時鐘週期來作調度。 非阻塞則容許多個線程同時進入臨界區。 2、鎖 死鎖 死鎖是進程死鎖的簡稱,是指多個進程循環等待他方佔有的資源而無限的僵持下去的局面。 給你們推薦一下q u n 678-241-563,若是你們對於學習Java有任何問題,學習方法,學習路線,如何學習有效率的問題,能夠隨時來諮詢我,或者缺乏系統學習資料的,我作這行年頭比較久,自認爲仍是比較有經驗的,能夠幫助你們提出建設性建議 活鎖 假設有兩個線程一、2,它們都須要資源 A/B,假設1號線程佔有了 A 資源,2號線程佔有了 B 資源;因爲兩個線程都須要同時擁有這兩個資源才能夠工做,爲了不死鎖,1號線程釋放了 A 資源佔有鎖,2號線程釋放了 B 資源佔有鎖;此時 AB 空閒,兩個線程又同時搶鎖,再次出現上述狀況,此時發生了活鎖。 簡單類比,電梯遇到人,一個進的一個出的,對面佔路,兩我的同時往一個方向讓路,來回重複,仍是堵着路。 若是線上應用遇到了活鎖問題,恭喜你中獎了,這類問題比較難排查。 飢餓 飢餓是指某一個或者多個線程由於種種緣由沒法得到所須要的資源,致使一直沒法執行。 線程的生命週期 在線程的生命週期中,它要經歷建立、可運行、不可運行幾種狀態。 3、建立狀態 當用 new 操做符建立一個新的線程對象時,該線程處於建立狀態。 處於建立狀態的線程只是一個空的線程對象,系統不爲它分配資源。 可運行狀態 執行線程的 start() 方法將爲線程分配必須的系統資源,安排其運行,並調用線程體——run()方法,這樣就使得該線程處於可運行狀態(Runnable)。 這一狀態並非運行中狀態(Running),由於線程也許實際上並未真正運行。 不可運行狀態 當發生下列事件時,處於運行狀態的線程會轉入到不可運行狀態: 調用了 sleep() 方法; 線程調用 wait() 方法等待特定條件的知足; 線程輸入/輸出阻塞; 返回可運行狀態; 處於睡眠狀態的線程在指定的時間過去後; 若是線程在等待某一條件,另外一個對象必須經過 notify() 或 notifyAll() 方法通知等待線程條件的改變; 若是線程是由於輸入輸出阻塞,等待輸入輸出完成。 線程的優先級 4、線程優先級及設置 線程的優先級是爲了在多線程環境中便於系統對線程的調度,優先級高的線程將優先執行。一個線程的優先級設置聽從如下原則: 線程建立時,子繼承父的優先級; 線程建立後,可經過調用 setPriority() 方法改變優先級; 線程的優先級是1-10之間的正整數。 線程的調度策略 線程調度器選擇優先級最高的線程運行。可是,若是發生如下狀況,就會終止線程的運行: 線程體中調用了 yield() 方法,讓出了對 CPU 的佔用權; 線程體中調用了 sleep() 方法,使線程進入睡眠狀態; 線程因爲 I/O 操做而受阻塞; 另外一個更高優先級的線程出現; 在支持時間片的系統中,該線程的時間片用完。 單線程建立方式 單線程建立方式比較簡單,通常只有兩種方式:繼承 Thread 類和實現 Runnable 接口;這兩種方式比較經常使用就不在 Demo 了,可是對於新手須要注意的問題有: 無論是繼承 Thread 類仍是實現 Runable 接口,業務邏輯是寫在 run 方法裏面,線程啓動的時候是執行 start() 方法; 開啓新的線程,不影響主線程的代碼執行順序也不會阻塞主線程的執行; 新的線程和主線程的代碼執行順序是不可以保證前後的; 對於多線程程序,從微觀上來說某一時刻只有一個線程在工做,多線程目的是讓 CPU 忙起來; 經過查看 Thread 的源碼能夠看到,Thread 類是實現了 Runnable 接口的,因此這兩種本質上來說是一個; PS:平時在工做中也能夠借鑑這種代碼結構,對上層調用來說提供更多的選擇,做爲服務提供方核心業務歸一維護 爲何要用線程池 經過上面的介紹,徹底能夠開發一個多線程的程序,爲何還要引入線程池呢。主要是由於上述單線程方式存在如下幾個問題: 線程的工做週期:線程建立所需時間爲 T1,線程執行任務所需時間爲 T2,線程銷燬所需時間爲 T3,每每是 T1+T3 大於 T2,全部若是頻繁建立線程會損耗過多額外的時間; 若是有任務來了,再去建立線程的話效率比較低,若是從一個池子中能夠直接獲取可用的線程,那效率會有所提升。因此線程池省去了任務過來,要先建立線程再去執行的過程,節省了時間,提高了效率; 線程池能夠管理和控制線程,由於線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控; 線程池提供隊列,存放緩衝等待執行的任務。 大體總結了上述的幾個緣由,因此能夠得出一個結論就是在平時工做中,若是要開發多線程程序,儘可能要使用線程池的方式來建立和管理線程。 經過線程池建立線程從調用 API 角度來講分爲兩種,一種是原生的線程池,另外該一種是經過 Java 提供的併發包來建立,後者比較簡單,後者實際上是對原生的線程池建立方式作了一次簡化包裝,讓調用者使用起來更方便,但道理都是同樣的。因此搞明白原生線程池的原理是很是重要的。 ThreadPoolExecutor 經過 ThreadPoolExecutor 建立線程池,API 以下所示: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue); 先來解釋下其中的參數含義(若是看的比較模糊能夠大體有個印象,後面的圖是關鍵)。 corePoolSize 核心池的大小。 在建立了線程池後,默認狀況下,線程池中並無任何線程,而是等待有任務到來才建立線程去執行任務,除非調用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,從這兩個方法的名字就能夠看出,是預建立線程的意思,即在沒有任務到來以前就建立 corePoolSize 個線程或者一個線程。默認狀況下,在建立了線程池後,線程池中的線程數爲0,當有任務來以後,就會建立一個線程去執行任務,當線程池中的線程數目達到 corePoolSize 後,就會把到達的任務放到緩存隊列當中。 maximumPoolSize 線程池最大線程數,這個參數也是一個很是重要的參數,它表示在線程池中最多能建立多少個線程。 keepAliveTime 表示線程沒有任務執行時最多保持多久時間會終止。默認狀況下,只有當線程池中的線程數大於 corePoolSize 時,keepAliveTime 纔會起做用,直到線程池中的線程數不大於 corePoolSize,即當線程池中的線程數大於 corePoolSize 時,若是一個線程空閒的時間達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。 可是若是調用了 allowCoreThreadTimeOut(boolean) 方法,在線程池中的線程數不大於 corePoolSize 時,keepAliveTime 參數也會起做用,直到線程池中的線程數爲0。 unit 參數 keepAliveTime 的時間單位。 workQueue 一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,通常來講,這裏的阻塞隊列有如下這幾種選擇:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。 threadFactory 線程工廠,主要用來建立線程。 handler 表示當拒絕處理任務時的策略,有如下四種取值: ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出 RejectedExecutionException 異常; ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常; ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程); ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務。 上面這些參數是如何配合工做的呢?請看下圖: 注意圖上面的序號。 5、線程池之間的參數協做 簡單總結下線程池之間的參數協做分爲如下幾步: 線程優先向 CorePool 中提交; 在 Corepool 滿了以後,線程被提交到任務隊列,等待線程池空閒; 在任務隊列滿了以後 corePool 尚未空閒,那麼任務將被提交到 maxPool 中,若是 MaxPool 滿了以後執行 task 拒絕策略。 流程圖以下: 以上就是原生線程池建立的核心原理。除了原生線程池以外併發包還提供了簡單的建立方式,上面也說了它們是對原生線程池的一種包裝,可讓開發者簡單快捷的建立所須要的線程池。 6、Executors newSingleThreadExecutor 建立一個線程的線程池,在這個線程池中始終只有一個線程存在。若是線程池中的線程由於異常問題退出,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。 newFixedThreadPool 建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。 newCachedThreadPool 可根據實際狀況,調整線程數量的線程池,線程池中的線程數量不肯定,若是有空閒線程會優先選擇空閒線程,若是沒有空閒線程而且此時有任務提交會建立新的線程。在正常開發中並不推薦這個線程池,由於在極端狀況下,會由於 newCachedThreadPool 建立過多線程而耗盡 CPU 和內存資源。 newScheduledThreadPool 此線程池能夠指定固定數量的線程來週期性的去執行。好比經過 scheduleAtFixedRate 或者 scheduleWithFixedDelay 來指定週期時間。 PS:另外在寫定時任務時(若是不用 Quartz 框架),最好採用這種線程池來作,由於它能夠保證裏面始終是存在活的線程的。 推薦使用 ThreadPoolExecutor 方式 在阿里的 Java 開發手冊時有一條是不推薦使用 Executors 去建立,而是推薦去使用 ThreadPoolExecutor 來建立線程池。 這樣作的目的主要緣由是:使用 Executors 建立線程池不會傳入核心參數,而是採用的默認值,這樣的話咱們每每會忽略掉裏面參數的含義,若是業務場景要求比較苛刻的話,存在資源耗盡的風險;另外採用 ThreadPoolExecutor 的方式可讓咱們更加清楚地瞭解線程池的運行規則,無論是面試仍是對技術成長都有莫大的好處。 改了變量,其餘線程能夠當即知道。保證可見性的方法有如下幾種: volatile 加入 volatile 關鍵字的變量在進行彙編時會多出一個 lock 前綴指令,這個前綴指令至關於一個內存屏障,內存屏障能夠保證內存操做的順序。當聲明爲 volatile 的變量進行寫操做時,那麼這個變量須要將數據寫到主內存中。 因爲處理器會實現緩存一致性協議,因此寫到主內存後會致使其餘處理器的緩存無效,也就是線程工做內存無效,須要從主內存中從新刷新數據。 文章到這裏就結束了 喜歡小編分享的技術文章能夠點贊關注哦! 小編這邊整理了一些Java核心知識點資料集錦,以及Java多線程,spring,微服務,MySQL調優,mybatis的面試題資料等....關注公衆號:麒麟改bug面試

相關文章
相關標籤/搜索