1. 程式人生 > >互聯網架構多線程並發編程高級教程(上)

互聯網架構多線程並發編程高級教程(上)

並發 取模 守護 事務 分段 喚醒 提升 程序 服務

#基礎篇幅:
線程基礎知識、並發安全性、JDK鎖相關知識、線程間的通訊機制、
JDK提供的原子類、並發容器、線程池相關知識點
?

#高級篇幅:
ReentrantLock源碼分析、對比兩者源碼,更加深入理解讀寫鎖,JAVA內存模型、先行發生原則、指令重排序
?
#環境說明:
idea、java8、maven

#第一章 並發簡介

?? ?##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提供的相應的並發工具類)

互聯網架構多線程並發編程高級教程(上)