Java多線程-零基礎學習

 

多線程

  1. 程序、進程、線程:
    1. 程序Program:指令集,靜態概念。
    2. 進程Process:操作系統,調度程序,動態概念。
      • 進程是程序的一次動態執行過程,佔用特定的地址空間
      • 每個進程都是獨立的,由三部分組成:cpu,data,code
      • 一個程序就是一個進程。
    3. 線程Thread:在進程內,多條執行路徑。(不同的執行路徑)
      • 一個進程可以擁有多個並行的(concurrent)線程。
      • 一個進程中的線程共享相同的內存單元/內存地址空間->可以訪問相同的變量和對象,而且它們從同一個堆中分配對象->通信、數據交換、同步操作。
      • 會造成併發的問題。
  2. 線程和進程的區別:
    1. 進程:作爲資源分配的單位
    2. 線程:調度和執行的單位
    3. 包含關係:
      • 沒有線程的進程,可以看做單線程的,如果一個進程內部擁有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的。
  3. 線程的基本概念
    1. 線程是一個程序內部的順序控制流。
    2. 線程和進程的區別
      • 每個進程都有獨立的代碼和數據空間(進程上下文),進程間的切換會有較大的開銷。
      • 線程可以看成時輕量級的進程,同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換的開銷小。
      • 多進程: 在操作系統中能同時運行多個任務(程序)
      • 多線程: 在同一應用程序中有多個順序流同時執行
      • Java的線程是通過java.lang.Thread類來實現的。
    3. 擴充
      • 一個進程是一個正運行的應用程序的實例 。它由兩個部分組成:一個是操作系統用來管理這個進程的內核對象。另一個是這個進程擁有的地址空間。從執行角度方面看,一個進程由一個或多個線程組成。
      • 一個線程是一個執行單元,它控制CPU執行進程中某一段代碼段。
      • 一個線程可以訪問這個進程中所有的地址空間和資源。
      • 一個進程最少包括一個線程來執行代碼,這個線程又叫做主線程。
      • 線程除了能夠訪問進程的資源外,每個線程還擁有自己的棧
  4. 線程的創建
    1. 第一種方式
      • 繼承Thread類,重寫run方法。
    2. 第二種
      • 實現Runnable接口,重寫run方法。
  5. 第二種方式代碼示例:
    1. public class Demo2 {
      	public static void main(String[] args) {
      		//1、創建真是角色
      		Programme p1 = new Programme();
      		//2、創建代理角色+真實角色引用
      		Thread thread = new Thread(p1);
      		//3、代理角色調用start方法開啓線程
      		thread.start();
      
      		for (int i = 0; i < 2000; i++) {
      			System.err.println("一邊查文檔");
      		}
      	}
      }
      /**
       * @author Johnny
       * @category 實現了Runnable接口
       */
      class Programme implements Runnable {
      
      	public void run() {
      		for (int i = 0; i < 2000; i++) {
      			System.out.println("一邊寫代碼");
      		}
      	}
      
      }

       

  6. 實現Runnable接口優點:
    1. 可以同時實現繼承。實現Runnable接口方式要通用一些。
    2. 避免單繼承。
    3. 方便共享資源,同一份資源,多個代理訪問。
  7. 線程的五個狀態
    1. 新生狀態:
      • 用new關鍵字和Thread類或其子類建立一個線程對象後,訪問線程對象就處於新生狀態。處於新生狀態的線程有自己的內存空間,通過調用start方法進入就緒狀態(runnable)
    2. 就緒狀態:
      • 處於就緒狀態的線程已經具備了運行條件,但還沒有分配到cpu,處於線程就緒隊列,等待系統爲其分配cpu。等待狀態並不是執行狀態。
    3. 運行狀態:
      • 在運行狀態和線程執行自己的run方法中代碼,直到調用其他方法而終止或等待某資源而阻塞或完成任務而終止。
    4. 阻塞狀態:
      • 處於運行狀態的線程在某些情況下,如執行了sleep(睡眠)方法,或等待I/O設備等資源,將讓出CPU並暫時停止自己的運行,進入阻塞狀態。
    5. 終止狀態:
      • 終止狀態是線程生命週期中的最後一個階段。線程終止的原因有兩個,一個是正常運行的線程完成了它的全部工作;另一種是線程被強制執行的終止,如:通過執行stop或destory方法來終止一個線程(前者會產生異常,後者是強制終止不會釋放鎖,都不推薦)
  8. 線程的常用方法:
    1. isAlive()
      • 判斷線程是否處於活動狀態,即線程是否還未終止。
    2. getPriority()
      • 獲得線程的優先級數值
    3. setPriority()
      • 設置線程的優先級數值
    4. Thread.sleep()
      • 將當前線程睡眠指定毫秒數
    5. join()
      • 調用某線程的該方法,將當前線程與該線程“合併”,即等待該線程結束,再恢復當前線程的運行。
    6. yield()
      • 讓出CPU,當前線程進入就緒隊列等待調度。
    7. wait()
      • 當前線程進入對象的wait pool。
    8. notify()/notifyAll()
      • 喚醒對象的wait pool中的一個/所有等待線程。
    9. static void sleep(long millis)
      •  在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
  9. interrupt
    1. 中斷線程的方法。
    2. 中斷線程同時拋出一個異常:InterruptedException。
    3. InterruptedException:該異常是編譯異常(也稱受檢查異常)。
  10. join()
    1. 實現了“整體插隊”的效果,將當前加入的線程執行完畢,然後繼續執行外面的線程。
    2. thread.Join把指定的線程加入到當前線程,可以將兩個交替執行的線程合併爲順序執行的線程。
      • 比如在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。
  11. yield()
    1. yield()應該做的是讓當前運行線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因爲讓步的線程還有可能被線程調度程序再次選中。
    2. 結論:yield()從未導致線程轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。
  12. 兩種線程模式:
    1. 協作式:一個線程保留對處理器的控制直到它自己決定放棄
      • 速度快、代價低
      • 用戶編程非常麻煩
    2. 搶先式:系統可以任意的從線程中奪回對CPU的控制權,再把控制權分給其它的線程 。
      • 兩次切換之間的時間間隔就叫做時間片
      • 效率不如協作式高 ,OS核心必須負責管理線程
      • 簡化編程,而且使程序更加可靠
    3. 多數線程的調度是搶先式的。
  13. synchronized
    1. 由於同一個進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。
    2. java語言提供了專門機制以解決這種衝突,有效的避免了同一個數據被多個線程同時訪問。
    3. 由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要針對方法提出一套機制,
    4. 這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊確保資源安全,線程安全。
    5. 關鍵字synchronized 來與對象的互斥鎖聯繫。當某個對象synchronized修飾時,表明該對象在任一時刻只能由一個線程訪問。
  14. Web12306線程安全代碼:
    1. public class Web12306XCAQ implements Runnable{
      
      	private int num = 20;//共享資源
      	private boolean flag = true;
      	
      	@Override
      	public void run() {
      		while(flag){
      			method2();
      		}
      	}
      
      	public synchronized void method1(){
      		if(num<1){
      			flag=false;
      		}else{
      			try {
      				Thread.sleep(200);
      			} catch (InterruptedException e) {
      				e.printStackTrace();
      			}
      			//說出當前線程的名稱,誰搶了第?張票
      			System.out.println(
      			Thread.currentThread().getName()+"搶了第"+num--+"張票");
      		}
      	}
      	
      	public void method2(){
      		synchronized (this) {
      			if(num<1){
      				flag=false;
      			}else{
      				try {
      					Thread.sleep(200);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      				//說出當前線程的名稱,誰搶了第?張票
      				System.out.println(
      				Thread.currentThread().getName()+"搶了第"+num--+"張票");
      			}
      		}
      	}
      }

       

  15. 線程死鎖
    1. 過多的同步容易造成死鎖
    2. 一個線程中鎖定了另一個線程需要的對象,而另一個線程鎖定了這個線程需要的對象。
    3. 也就是說:兩個線程互相鎖定對方所需要的對象。
  16. synchronized總結
    1. 無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是鎖在了對象上,而不是把一段代碼或函數當作鎖,而且同步方法很可能還會被其他線程的對象訪問。
    2. 每個對象只有一個鎖(lock)與之相關聯。
    3. 實現同步是要很大的系統開銷作爲代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
    4. 搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序。
  17. wait() :等待、釋放鎖
  18. notify()、notifyAll() :喚醒
    1. 他們三個都是在同步中使用
    2. 生產者繳費者模式,解決線程死鎖問題(是一個解決方案)
    3. 該問題的關鍵就是要保證生產者不會再緩衝區滿時加入數據,消費者也不會再緩衝區空時消耗數據。
    4. 通常常用的方法有信號燈法。
  19. Wait sleep區別
    1. 來源不同
      • Sleep是Thread提供的方法
      • Wait來自Object
    2. 代碼位置不同
      • Wait需要寫在Synchronize語句塊裏面
    3. 是否釋放鎖定對象
    4. 調用wait方法,釋放鎖定該對象
    5. Sleep時別的線程也不可以訪問鎖定對象

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章