#基礎篇幅:
線程基礎知識、併發安全性、JDK鎖相關知識、線程間的通信機制、
JDK提供的原子類、併發容器、線程池相關知識點
java
#高級篇幅:
ReentrantLock源碼分析、對比二者源碼,更加深刻理解讀寫鎖,JAVA內存模型、先行發生原則、指令重排序
#環境說明:
idea、java八、mavenweb
#第一章 併發簡介面試
##01 課程簡介 爲何要學習併發編程? 方便實際開發 面試 課程特色 適合羣體 ##02什麼是併發編程 併發歷史: 早期計算機--從頭至尾執行一個程序,資源浪費 操做系統出現--計算機能運行多個程序,不一樣的程序在不一樣的單獨的進程中運行 一個進程,有多個線程 提升資源的利用率,公平 串行與並行的區別: 串行:洗茶具、打水、燒水、等水開、沖茶 並行:打水、燒水同時洗茶具、水開、沖茶 好處:能夠縮短整個流程的時間 併發編程目的: 摩爾定律:當價格不變時,集成電路上可容納的元器件的數目,約每隔18-24個月便會增長一倍,性能也將提高一倍。這必定律揭示了信息技術進步的速度。 讓程序充分利用計算機資源 加快程序響應速度(耗時任務、web服務器) 簡化異步事件的處理 何時適合使用併發編程: 任務會阻塞線程,致使以後的代碼不能執行:好比一邊從文件中讀取,一邊進行大量計算的狀況 任務執行時間過長,能夠劃分爲分工明確的子任務:好比分段下載 任務間斷性執行:日誌打印 任務自己須要協做執行:好比生產者消費者問題 ##03併發編程的挑戰之頻繁的上下文切換 cpu爲線程分配時間片,時間片很是短(毫秒級別),cpu不停的切換線程執行,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠再加載這 個任務的狀態,讓咱們感受是多個程序同時運行的 上下文的頻繁切換,會帶來必定的性能開銷 如何減小上下文切換的開銷? 無鎖併發編程 無鎖併發編程。多線程競爭鎖時,會引發上下文切換,因此多線程處理數據時,能夠用一 些辦法來避免使用鎖,如將數據的ID按照Hash算法取模分段,不一樣的線程處理不一樣段的數據 CAS Java的Atomic包使用CAS算法來更新數據,而不須要加鎖。 使用最少線程。 避免建立不須要的線程,好比任務不多,可是建立了不少線程來處理,這 樣會形成大量線程都處於等待狀態。 協程 在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換。--GO ##04 併發編程的挑戰之死鎖 package com.xdclass.synopsis; /** * 死鎖Demo */ public class DeadLockDemo { private static final Object HAIR_A = new Object(); private static final Object HAIR_B = new Object(); public static void main(String[] args) { new Thread(()->{ synchronized (HAIR_A) { try { Thread.sleep(50L); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (HAIR_B) { System.out.println("A成功的抓住B的頭髮"); } } }).start(); new Thread(()->{ synchronized (HAIR_B) { synchronized (HAIR_A) { System.out.println("B成功抓到A的頭髮"); } } }).start(); } } ##05 併發編程的挑戰之線程安全 package com.xdclass.synopsis; import java.util.concurrent.CountDownLatch; /** * 線程不安全操做代碼實例 */ public class UnSafeThread { private static int num = 0; private static CountDownLatch countDownLatch = new CountDownLatch(10); /** * 每次調用對num進行++操做 */ public static void inCreate() { num++; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ for (int j = 0; j < 100; j++) { inCreate(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } //每一個線程執行完成以後,調用countdownLatch countDownLatch.countDown(); }).start(); } while (true) { if (countDownLatch.getCount() == 0) { System.out.println(num); break; } } } } ##06 併發編程的挑戰之資源限制 硬件資源 服務器: 1m 本機:2m 帶寬的上傳/下載速度、硬盤讀寫速度和CPU的處理速度。 軟件資源 數據庫鏈接 500個鏈接 1000個線程查詢 並不會所以而加快 socket
#第二章----線程基礎算法
##01 進程與線程的區別 進程:是系統進行分配和管理資源的基本單位 線程:進程的一個執行單元,是進程內調度的實體、是CPU調度和分派的基本單位,是比進程更小的獨立運行的基本單位。線程也被稱爲輕量級進程,線程是程序執行的最小單位。 一個程序至少一個進程,一個進程至少一個線程。 進程有本身的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,創建數據表來維護代碼段、堆棧段和數據段,這種操做很是昂貴。 而線程是共享進程中的數據的,使用相同的地址空間,所以CPU切換一個線程的花費遠比進程要小不少,同時建立一個線程的開銷也比進程要小不少。 線程之間的通訊更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通訊須要以通訊的方式進行。 如何處理好同步與互斥是編寫多線程程序的難點。 多進程程序更健壯,進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響, 而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,因此可能一個線程 出現問題,進而致使整個程序出現問題 ##02 線程的狀態及其相互轉換 初始(NEW):新建立了一個線程對象,但尚未調用start()方法。 運行(RUNNABLE):處於可運行狀態的線程正在JVM中執行,但它可能正在等待來自操做系統的其餘資源,例如處理器。 阻塞(BLOCKED):線程阻塞於synchronized鎖,等待獲取synchronized鎖的狀態。 等待(WAITING):Object.wait()、join()、 LockSupport.park(),進入該狀態的線程須要等待其餘線程作出一些特定動做(通知或中斷)。 超時等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,該狀態不一樣於WAITING, 它能夠在指定的時間內自行返回。 終止(TERMINATED):表示該線程已經執行完畢。 ##03 建立線程的方式(上) 1.繼承Thread,並重寫父類的run方法 2.實現Runable接口,並實現run方法 實際開發中,選第2種:java只容許單繼承 增長程序的健壯性,代碼能夠共享,代碼跟數據獨立 ##04 建立線程的方式(下) 1.使用匿名內部類 2.Lambda表達式 3.線程池 ##05 線程的掛起跟恢復 什麼是掛起線程? 線程的掛起操做實質上就是使線程進入「非可執行」狀態下,在這個狀態下CPU不會分給線程時間片,進入這個狀態能夠用來暫停一個線程的運行。 在線程掛起後,能夠經過從新喚醒線程來使之恢復運行 爲何要掛起線程? cpu分配的時間片很是短、同時也很是珍貴。避免資源的浪費。 如何掛起線程? 被廢棄的方法 thread.suspend() 該方法不會釋放線程所佔用的資源。若是使用該方法將某個線程掛起,則可能會使其餘等待資源的線程死鎖 thread.resume() 方法自己並沒有問題,可是不能獨立於suspend()方法存在 可使用的方法 wait() 暫停執行、放棄已經得到的鎖、進入等待狀態 notify() 隨機喚醒一個在等待鎖的線程 notifyAll() 喚醒全部在等待鎖的線程,自行搶佔cpu資源 何時適合使用掛起線程? 我等的船還不來(等待某些未就緒的資源),我等的人還不明白。直到notify方法被調用 ##06 線程的中斷操做 stop() 廢棄方法,開發中不要使用。由於一調用,線程就馬上中止,此時有可能引起相應的線程安全性問題 Thread.interrupt方法 自行定義一個標誌,用來判斷是否繼續執行 ##07 線程的優先級 線程的優先級告訴程序該線程的重要程度有多大。若是有大量線程都被堵塞,都在等候運行,程序會盡量地先運行優先級的那個線程。 可是,這並不表示優先級較低的線程不會運行。若線程的優先級較低,只不過表示它被准許運行的機會小一些而已。 線程的優先級設置能夠爲1-10的任一數值,Thread類中定義了三個線程優先級,分別是: MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10),通常狀況下推薦使用這幾個常量,不要自行設置數值。 不一樣平臺,對線程的優先級的支持不一樣。 編程的時候,不要過分依賴線程優先級,若是你的程序運行是否正確取決於你設置的優先級是否按所設置的優先級運行,那這樣的程序不正確 任務: 快速處理:設置高的優先級 慢慢處理:設置低的優先級 ##08 守護線程 線程分類 用戶線程、守護線程 守護線程:任何一個守護線程都是整個程序中全部用戶線程的守護者,只要有活着的用戶線程,守護線程就活着。當JVM實例中最後一個非守護線程結束時,也隨JVM一塊兒退出 守護線程的用處:jvm垃圾清理線程 建議: 儘可能少使用守護線程,因其不可控 不要在守護線程裏去進行讀寫操做、執行計算邏輯
#第三章----線程安全性問題數據庫
##01 什麼是線程安全性? 當多個線程訪問某個類,無論運行時環境採用何種調度方式或者這些線程如何交替執行,而且在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類爲線程安全的。----《併發編程實戰》 什麼是線程不安全? 多線程併發訪問時,得不到正確的結果。 ##02 從字節碼角度剖析線程不安全操做 javac -encoding UTF-8 UnsafeThread.java 編譯成.class javap -c UnsafeThread.class 進行反編譯,獲得相應的字節碼指令 0: getstatic #2 獲取指定類的靜態域,並將其押入棧頂 3: iconst_1 將int型1押入棧頂 4: iadd 將棧頂兩個int型相加,將結果押入棧頂 5: putstatic #2 爲指定類靜態域賦值 8: return 例子中,產生線程不安全問題的緣由: num++ 不是原子性操做,被拆分紅好幾個步驟,在多線程併發執行的狀況下,由於cpu調度,多線程快遞切換,有可能兩個同一時刻都讀取了同一個num值,以後對它進行+1操做,致使線程安全性。 ##03 原子性操做 什麼是原子性操做 一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。 A想要從本身的賬戶中轉1000塊錢到B的賬戶裏。那個從A開始轉賬,到轉賬結束的這一個過程,稱之爲一個事務。在這個事務裏,要作以下操做: 1. 從A的賬戶中減去1000塊錢。若是A的賬戶原來有3000塊錢,如今就變成2000塊錢了。 2. 在B的賬戶里加1000塊錢。若是B的賬戶若是原來有2000塊錢,如今則變成3000塊錢了。 若是在A的賬戶已經減去了1000塊錢的時候,突然發生了意外,好比停電什麼的,致使轉賬事務意外終止了,而此時B的賬戶裏尚未增長1000塊錢。 那麼,咱們稱這個操做失敗了,要進行回滾。回滾就是回到事務開始以前的狀態,也就是回到A的賬戶還沒減1000塊的狀態,B的賬戶的原來的狀態。 此時A的賬戶仍然有3000塊,B的賬戶仍然有2000塊。 通俗點講:操做要成功一塊兒成功、要失敗你們一塊兒失敗 如何把非原子性操做變成原子性 volatile關鍵字僅僅保證可見性,並不保證原子性 synchronize關機字,使得操做具備原子性 ##04 深刻理解synchronized 內置鎖 每一個java對象均可以用作一個實現同步的鎖,這些鎖稱爲內置鎖。線程進入同步代碼塊或方法的時候會自動得到該鎖,在退出同步代碼塊或方法時會釋放該鎖。得到內置鎖的惟一途徑就是進入這個鎖的保護的同步代碼塊或方法。 互斥鎖 內置鎖是一個互斥鎖,這就是意味着最多隻有一個線程可以得到該鎖,當線程A嘗試去得到線程B持有的內置鎖時,線程A必須等待或者阻塞,直到線程B釋放這個鎖,若是B線程不釋放這個鎖,那麼A線程將永遠等待下去。 修飾普通方法:鎖住對象的實例 修飾靜態方法:鎖住整個類 修飾代碼塊: 鎖住一個對象 synchronized (lock) 即synchronized後面括號裏的內容 ##05 volatile關鍵字及其使用場景 能且僅能修飾變量 保證該變量的可見性,volatile關鍵字僅僅保證可見性,並不保證原子性 禁止指令重排序 A、B兩個線程同時讀取volatile關鍵字修飾的對象 A讀取以後,修改了變量的值 修改後的值,對B線程來講,是可見 使用場景 1:做爲線程開關 2:單例,修飾對象實例,禁止指令重排序 ##06 單例與線程安全 餓漢式--自己線程安全 在類加載的時候,就已經進行實例化,不管以後用不用到。若是該類比較佔內存,以後又沒用到,就白白浪費了資源。 懶漢式 -- 最簡單的寫法是非線程安全的 在須要的時候再實例化 ##07 如何避免線程安全性問題 線程安全性問題成因 1:多線程環境 2:多個線程操做同一共享資源 3:對該共享資源進行了非原子性操做 如何避免 打破成因中三點任意一點 1:多線程環境--將多線程改單線程(必要的代碼,加鎖訪問) 2:多個線程操做同一共享資源--不共享資源(ThreadLocal、不共享、操做無狀態化、不可變) 3:對該共享資源進行了非原子性操做-- 將非原子性操做改爲原子性操做(加鎖、使用JDK自帶的原子性操做的類、JUC提供的相應的併發工具類)