1. 程式人生 > 實用技巧 >java多執行緒--【Foam番茄】

java多執行緒--【Foam番茄】

程序

是系統資源分配的單位

執行緒

通常在一個程序中可以包含若干個執行緒,當然一個程序中至少有一個執行緒,不然沒有存在的意義。執行緒是cpu排程和執行的單位

注意:很多多執行緒是模擬出來的,真正的多執行緒是指有多個cpu,即多核,如伺服器。如果是模擬出來的多執行緒,即在一個cpu的情況下,在同一個時間點,cpu只能執行一個程式碼,因為切換的很快,所以就有同時執行的錯覺
  • 執行緒就是獨立的執行路徑
  • 在程式執行時,即使沒有自己建立執行緒,後臺也會有多個執行緒,如主執行緒,gc執行緒
  • main()稱之為主執行緒,為系統的入口,用於執行整個程式
  • 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統密切相關的,先後順序是不能人為的干預的
  • 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
  • 執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷
  • 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致

執行緒建立

三種建立方式

Thread class ==》》繼承Thread類(重點)

Runnable介面 ==》》實現Runnable介面(重點)

Callable介面 ==》》實現Callable介面(瞭解)

Thread

  • 自定義執行緒類繼承Thread類
  • 重寫run()方法,編寫執行緒執行體
  • 建立執行緒物件,呼叫start()方法啟動執行緒

總結:注意,執行緒開啟不一定立即執行,由cpu排程執行

Callable

  • 可以定義返回值
  • 可以丟擲異常

Runnable

  • 定義MyRunnable類實現Runnable介面
  • 實現run() 方法,編寫執行緒執行體
  • 建立執行緒物件,呼叫start() 方法啟動執行緒

推薦使用Runnable物件,因為java單繼承的侷限性

小結

繼承Thread類

  • 子類繼承Thread類具備多執行緒能力
  • 啟動執行緒:子類物件.start()
  • 不建議使用:避免oop單繼承侷限性

實現Runnable介面

  • 實現介面Runnable具有多執行緒能力
  • 啟動執行緒:傳入目標物件+Thread物件.start()
  • 推薦使用:避免單繼承侷限性,靈活方便,方便同一個物件被多個執行緒使用

實現Callable介面(瞭解即可)

  • 實現Callable介面,需要返回值型別
  • 重寫call方法,需要丟擲異常
  • 建立目標物件
  • 建立執行服務:ExecutorService ser = Executors.newFixedThreadPool(1)
  • 提交執行:Futureresult1=ser.submit(t1)
  • 獲取結果:boolean r1=result1.get();
  • 關閉服務:ser.shutdownNow()

靜態代理

真實物件和代理物件都要實現同一個介面

代理物件要代理真實角色

好處

代理物件可以做很多真實物件做不了的事情

真實物件專注做自己的事情

Lamda表示式

  • λ希臘字母表中排序第十一位的字母,英語名稱為Lambda
  • 避免匿名內部類定義過多
  • 其實質屬於函數語言程式設計的概念

為什麼要使用lambda表示式

  • 避免匿名內部類定義過多
  • 可以讓你的程式碼看起來很簡潔
  • 去掉了一堆沒有意義的程式碼,只留下核心的邏輯

也許你會說,我看了Lambda表示式,不但不覺得簡潔,反而覺得更亂,看不懂了。那是因為我們還沒有習慣,用的多了,看習慣了,就好了

理解Functional Interface (函式式介面)是學習java8 lambda表示式的關鍵所在

函式式介面的定義:

	任何介面,如果只包含唯一一個抽象方法,那麼它就是一個函式式介面
public interface Runnable{
	public abstract void run();
}
	對於函式式介面,我們可以通過lambda表示式來建立該介面的物件
		// 1.lambda表示式簡化
		ILove love =(int a)->{
				System.out.println("I love you-->"+a);
		};
		
		// 2.簡化引數型別
		ILove love=(a)->{
			System.out.println("I love you-->"+a);
		};
		
		// 3.簡化括號
		ILove love=a->{
			System.out.println("I love you-->"+a);
		};
		
		// 3.去掉花括號
		ILove love=(a,b)->
		System.out.println("I love you-->"+a+b);
		
		// 總結:lambda表示式只能有一行程式碼的情況下才能簡化成為一行,如果有多行,那麼就用程式碼塊包裹
		// 前提是介面為函式式介面
		// 多個引數也可以去掉引數型別,要去掉就都去掉,必須加上括號

執行緒狀態


執行緒休眠

  • sleep(時間)指定當前執行緒阻塞的毫秒數
  • sleep存在異常InterruptedException;
  • sleep時間達到後執行緒進入就緒狀態
  • sleep可以模擬網路延遲,倒計時等
  • 每一個物件都有一個鎖,sleep不會釋放鎖

執行緒禮讓

  • 禮讓執行緒,讓當前正在執行的執行緒暫停,但不阻塞
  • 將執行緒從執行狀態轉為就緒狀態
  • 讓cpu重新排程,禮讓不一定成功!看cpu心情

Join

  • join合併執行緒,待此執行緒執行完成後,再執行其他執行緒,其他執行緒阻塞
  • 可以想象成插隊

守護執行緒(daemon)

  • 執行緒分為使用者執行緒守護執行緒
  • 虛擬機器必須確保使用者執行緒執行完畢
  • 虛擬機器不用等待守護執行緒執行完畢
  • 如,後臺記錄操作日誌,監控記憶體,垃圾回收等待……

執行緒同步

由於同一程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問衝突問題,為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可,存在以下問題:

  • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
  • 在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題
  • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題。

多個執行緒操作同一個資源

執行緒同步其實就是一種等待機制,多個需要同時訪問此物件得執行緒進入這個物件的等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用

同步方法

  • 由於我們可以用過private關鍵字來保證資料物件只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊

    同步方法:public synchronized void method(int args){}
    
  • synchronized方法控制物件的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行

    缺陷:若將一個大的方法申明為synchronized將會影響效率
    

    鎖的物件就是變化的量,需要增刪改的物件

同步方法弊端

  • 方法裡面需要修改的內容才需要鎖,鎖的太多,浪費資源

同步塊

  • 同步塊:synchronized(Obj){}
  • Obj稱之為同步監視器
    • Obj可以是任何物件,但是推薦使用共享資源作為同步監視器
    • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個物件本身,或者是class
  • 同步監視器的執行過程
    • 第一個執行緒訪問,鎖定同步監視器,執行其中程式碼
    • 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問
    • 第一個執行緒訪問完畢,解鎖同步監視器
    • 第二個執行緒訪問,發現同步監視器沒有鎖,然後鎖定並訪問

死鎖

  • 多個執行緒各自佔有一些共享資源,並且互相等待其他執行緒佔有的資源才能執行,而導致兩個或者多個執行緒都在等待對方釋放資源,都停止執行的情節,某一個同步塊同時擁有“兩個以上物件的鎖”是,就可能會發生“死鎖”的問題

死鎖避免方法

產生死鎖的四個必要條件

  • 互斥條件:一個資源每次只能被一個程序使用
  • 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放
  • 不剝奪條件:程序已獲得的資源,在未使用完之前,不能強行剝奪
  • 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係

上面列出了死鎖的四個必要條件,我們只要想辦法破其中的任意一個或多個條件就可以避免死鎖發生

synchronized與Lock的對比

  • Lock是顯示鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放

  • Lock只有程式碼塊鎖,synchronization有程式碼塊鎖和方法鎖

  • 使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好。並且具有更好的擴充套件性(提供更多的子類)

  • 優先使用順序

    Lock》同步程式碼塊(已經進入了方法體,分配了相應資源)》同步方法(在方法體之外)

    // 定義lock鎖   可重用鎖
    private final ReentrantLock lock=new ReentrantLock();
    

執行緒協作

使用執行緒池

  • 背景:經常建立和銷燬,使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大
  • 思路:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中。可以避免頻繁建立銷燬,實現重複利用。類似生活中的公共交通工具
  • 好處:
    • 提高響應速度(減少了建立新執行緒的時間)
    • 降低資源消耗(重複利用執行緒池中執行緒,不需要每次都建立)
    • 便於執行緒管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大執行緒數
      • keepAliveTime:執行緒沒有任務時最多保持多長時間後會終止
      • newFixedThreadPool 引數位:執行緒池大小