技術理論-【Thread】- java線程知識總結

概念:

背景

  • 程序,進程,線程,多任務,主線程
  • 三高應用(高可用,高性能,高併發)
  • 學習理論(守破離,斷舍離),
  • lambda(JDK8,內部類<靜態,局部,匿名,lambda>,函數式編程),
  • 定時器(timer,task),
  • 類協作(設計模式),代理模式(靜態代理<婚慶公司>,動態代理,真實角色,代理角色),單例模式,
  • CAS(compare and swap),悲觀鎖,樂觀鎖

知識點

  • 線程實現(Thread,Runnable,Callable),
  • 線程啓動(方式,時機),
  • 線程狀態(new新生,runnable<就緒,運行>,terminated死亡,block阻塞,time_wait時間等待<join,sleep,wait>,wait阻塞等待<wait,notify>),
  • 同步synchronize(方法,塊,容器),
  • 數據可見性和代碼有序性(volatile),
  • 守護線程,
  • ThreadLocal,JUC
  • happen before,CPU指令重排機制

實際問題

  • 共享資源問題(併發問題),
  • 併發案例(龜兔賽跑,12306,電影院,取錢),
  • 共享資源(全局對象、變量、方法),
  • 死鎖(哲學家就餐問題),
  • 線程協作,生產者消費者模型(緩存法<wait,notify>,信號燈法),
  • 單例模式使用了dubble checked。

線程命題

  1. 線程是爲了解決應用的多任務並行問題。
  2. 線程是輕量級的進程。
  3. 線程並行是單CPU模擬並行,所以線程會爭奪CPU的使用時間。
  4. 應用併發是某一功能被多人同時使用。
  5. 應用併發造成線程併發問題、服務器資源問題。
  6. 線程併發問題是線程調度機制和操作公共資源共同造成的。
  7. 線程調度機制衍生出線程狀態和改變調度邏輯問題。
  8. 線程併發問題有三個因素,原子性、可見性、有序性。
  9. synchronize來解決原子性。
  10. synchronize可能造成死鎖問題。
  11. synchronize是悲觀鎖。
  12. ThreadLocal是樂觀鎖。
  13. volatile解決數據可見性和代碼有序性。
  14. volatile用來解決CPU指令重排造成的髒數據問題。
  15. 生產者消費者模型是解決併發問題的異步方案。
  16. synchronize是解決併發問題的同步方案。

演化過程

線程的來歷

  1. 計算機簡化模型:程序+計算。
  2. 計算機使用CPU代替人工計算。
  3. 程序是算法+數據。
  4. 操作系統也是一種程序,可以運行應用程序。
  5. 操作系統管理計算機硬件和程序執行過程。
  6. 計算機只能一次運行一個程序
  7. 操作系統通過進程調度機制實現計算機同時運行多個程序。
  8. 進程是程序的一次執行過程。
  9. 每個進程有獨立內存空間。
  10. 進程調度機制讓多個進程輪流使用CPU。
  11. 最初一個程序執行一個任務。
  12. 線程讓程序同時執行多個任務。
  13. 線程調度機制讓多個線程輪流使用CPU
  14. 線程使用進程的內存空間和CPU時間。
    在這裏插入圖片描述

創建線程方式

  1. 繼承Thread類;
    • 對象可以直接調用start()啓動。
  2. 實現Runnable接口,線程不可以返回數據和拋異常;
    • 對象需要放入Thead代理對象後,代理對象調用start()啓動。
  3. 實現Callable接口,線程可以返回數據和拋異常;
    • 對象放入Thead代理對象後,代理對象調用start()啓動。
    • 對象放入FutureTask代理對象後,使用ExecutorService線程池對象的execute()啓動。

線程狀態

  1. 創建狀態(NEW):創建一個線程對象。
  2. 運行狀態(RUNNABLE):線程啓動後,爭取或者獲得CPU使用權。
  3. 死亡狀態(TERMINATED):線程邏輯(run方法)運行完畢。
  4. 時間等待狀態(TIME_WAIT):等待指定時間後(sleep/join/yield),才能爭取CPU使用權。
  5. 等待狀態(WAIT):等待別人喚醒(notify)。
  6. 阻塞狀態(BLOCK):獲取同步鎖失敗,等待別人釋放鎖。
    在這裏插入圖片描述

線程併發問題(原子性:代碼級別)

問題原因:CPU使用權
  1. 多個線程同時操作公共資源。
  2. 由於線程調度機制,線程被剝奪了CPU使用權,導致暫停寫入步驟。
  3. 其他線程可能拿到未修改之前的髒數據。
  4. 從而出現數據不一致問題。
解決方案:悲觀鎖
  1. 使用synchronize同步鎖機制。
  2. 操作公共資源之前,先鎖定資源使用權,阻止其他人使用資源。
  3. 將資源操作步驟原子化。
  4. 所有資源操作步驟都完成後,才釋放資源鎖。
  5. 加鎖的資源類型:
    • 同步方法,等同於鎖定this對象(一個線程對象啓動多個線程的情況);
    • 同步塊,等同於鎖定數據對象(class對象也可以鎖定=鎖定new操作);
      在這裏插入圖片描述

線程併發問題(可見性:指令級別)

問題原因:CPU寄存器緩存
  1. 線程指令運行時,會從主存中拷貝數據到寄存器;
  2. 由於寄存器中的數據拷貝,多線程操作同一個主存數據時;
  3. 會出現拷貝數據不一致;

解決方案:volatile

  1. volatile申明的變量,在修改後會標識緩存對象爲無效;
  2. 緩存無效後,CPU會重新加載主存數據;
    在這裏插入圖片描述

線程併發問題(有序性:代碼級別)

問題原因:JVM指令重排
  1. JVM在保證代碼執行效果一直的情況,進行的指令順序重新排列;
  2. 多線程情況下,變量變化順序,可能造成邏輯判斷混亂;
int i = 0;              //語句1            
boolean flag = false;   //語句2 
i = 1;                  //語句3  
flag = true;            //語句4

執行順序可能是:1234、1243、2134、2143。

解決方案:volatile

  1. volatile申明的變量,在編譯時,會保證改變量操作前後代碼不變;
  2. 比如5條代碼,變量是第3條,那麼12不會到3之後,45不會到3之前;
  3. 只能是:12345,12354,21345,21354
int i = 0;              //語句1            
boolean flag = false;   //語句2 
ticket = 99;			//語句3
i = 1;                  //語句4 
flag = true;            //語句5

火車票併發案例

/**
 * 線程同步機制:synchroneze
 * 使用同步塊的方式,創建一個鎖對象lock;
 * sleep會繼續佔用lock;
 * 
 * @author jeadong
 *
 */
public class SynchronizeTest {

	public static void main(String[] args)  {
		//啓動3個買票線程,模擬併發搶票
		new Thread(new Web12306()).start();
		new Thread(new Web12306()).start();
		new Thread(new Web12306()).start();
	}

}

//模擬12306買票操作
class Web12306 implements Runnable{

	private static Object lock = new Object();//買票鎖,保證減庫存操作的線程安全
	private static int tikects = 99;//總票數
	private boolean flag = true; 
	
	@Override
	public void run() {
		while(flag){
			//買票
			buyTikect();
		}//while end
	}

	private void buyTikect() {
		//買票前,先加鎖
		synchronized(lock){
			if (tikects > 0) {
				tikects--;
				System.out.println("【"+Thread.currentThread().getName()+"】購票成功!剩餘票量:"+tikects);
			}else{
				flag=false;//票賣光了,就結束買票
			}
			//模擬買票操作延遲
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}//sycn end
	}
	
}

單例模式案例,double checked

/**
 * 雙重檢測鎖:synchronize+volatile
 * 創建單例模式
 * 1、保證原子性,用synchronize鎖定class
 * 2、保證可見性和有序性,用volatile直接訪問主存對象
 * 3、最外層的判空是爲了提升訪問效率
 * 
 * @author jeadong
 *
 */
public class DubbleCheckedTest {

	public static void main(String[] args) {
		//啓動子線程觸發第一次訪問
		new Thread(() -> {
			System.out.println(Singleton.getInstance());
		}).start();
		//主線程觸發第二次訪問
		System.out.println(Singleton.getInstance());
		
	}

}

class Singleton {
	//volatile必須加,爲了防止指令重排,第一次創建時,內存還沒完成創建,就把對象指針暴露出去了
	private static volatile Singleton instance;
	
	private Singleton(){
	}

	public static Singleton getInstance(){
		//第一層檢測,爲了第二次訪問之後提升效率,不要再次加鎖
		if (null == instance) {
			//鎖定class,讓其他線程無法創建對象,保證只會執行一次New操作
			//如果沒有加鎖,就能模擬出創建兩個對象
			synchronized(Singleton.class){
				//第二層檢測,保證第一次訪問順利完成對象創建
				if(null == instance){
					//模擬創建對象比較耗時
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//創建對象
					instance = new Singleton();
				}
			}
		}
		
		return instance;
	}
	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章