五分鐘帶你詳細理解Java多線程

多線程

  • 在傳統的操作系統中,程序並不能獨立運行,作爲資源分配和獨立運行的基本單位都是進程。 在未配置 OS
  • 的系統中,程序的執行方式是順序執行,即必須在一個程序執行完後,才允許另一個程序執行;在多道程序環境下,則允許多個程序併發執行。程序的這兩種執行方式間有着顯著的不同。也正是程序併發執行時的這種特徵,才導致了在操作系統中引入進程的概念。
  • 自從在 20 世紀 60 年代人們提出了進程的概念後,在 OS 中一直都是以進程作爲能擁有資源和獨立運行的基本單位的。直到 20 世紀 80年代中期,人們又提出了比進程更小的能獨立運行的基本單位——線程(Thread),試圖用它來提高系統內程序併發執行的程度,從而可進一步提高系統的吞吐量。特別是在進入20 世紀 90年代後,多處理機系統得到迅速發展,線程能比進程更好地提高程序的並行執行程度,充分地發揮多處理機的優越性,因而在近幾年所推出的多處理機 OS中也都引入了線程,以改善 OS 的性能。
  • ----------------以上摘自《計算機操作系統-湯小丹等編著-3 版》

進程的概念

  • 就是正在運行的程序。也就是代表了程序鎖佔用的內存區域。

特點

  • 獨立性:進程是系統中獨立存在的實體,它可以擁有自己的獨立的資源,每一個進程都擁有自己私有的地址空間。在沒有經過進程本身允許的情況下,一個用戶進程不可以直接訪問其他進程的地址空間。

  • 動態性:進程與程序的區別在於,程序只是一個靜態的指令集合,而進程是一個正在系統中活動的指令集合。在進程中加入了時間的概念,進程具有自己的生命週期和各種不同的狀態,這些概念在程序中都是不具備的。

  • 併發性:多個進程可以在單個處理器上併發執行,多個進程之間不會互相影響。


線程的概念

  • 線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一個進程可以開啓多個線程。

  • 多線程擴展了多進程的概念,使得同一個進程可以同時併發處理多個任務。

  • 簡而言之,一個程序運行後至少一個進程,一個進程裏包含多個線程。

  • 如果一個進程只有一個線程,這種程序被稱爲單線程。

  • 如果一個進程中有多條執行路徑被稱爲多線程程序。
    在這裏插入圖片描述


進程和線程的關係
在這裏插入圖片描述
從上圖中可以看出一個操作系統中可以有多個進程,一個進程中可以有多個線程,每個進程有自己獨立的內存,每個線程共享一個進程中的內存,每個線程又有自己獨立的內存。(記清這個關係,非常重要!) 所以想使用線程技術,得先有進程,進程的創建是OS創建的,你能實現嗎?不能,一般都是c或者c++語言完成的。

多線程的特性

  • 隨機性
    在這裏插入圖片描述
    線程狀態
    在這裏插入圖片描述
    線程生命週期,總共有五種狀態:

  • 1) 新建狀態(New):當線程對象對創建後,即進入了新建狀態,如:Thread t = new MyThread();

  • 2) 就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,並不是說執行了t.start()此線程立即就會執行;

  • 3) 運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

  • 4) 阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,纔有機會再次被CPU調用以進入到運行狀態;

  • 5) 根據阻塞產生的原因不同,阻塞狀態又可以分爲三種:

  • a) 等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;
  • b) 同步阻塞:線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態;
  • c) 其他阻塞:通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
  • 6) 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

多線程創建1:繼承Thread

Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法就是通過Thread類的start()實例方法。Start()方法是一個native方法,它將通知底層操作系統,最終由操作系統啓動一個新線程,操作系統將執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啓動新線程並執行自己定義的run()方法。(模擬開啓多個線程,每個線程調用run()方法.)

常用方法

String getName()

      返回該線程的名稱。 

static Thread currentThread()

      返回對當前正在執行的線程對象的引用。 

void setName(String name)

      改變線程名稱,使之與參數 name 相同。

static void sleep(long millis)

 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。

void start()

      使該線程開始執行;Java 虛擬機調用該線程的 run 方法。

Thread(String name)

      分配新的 Thread 對象。

測試

public class Test1  {
    public static void main(String[] args) {
       //3、創建線程對象
       ThreadDemo t1 = new ThreadDemo("鋼鐵俠");
       ThreadDemo t2 = new ThreadDemo("美隊");
       //4、開啓線程:誰搶到資源誰就先執行
       t1.start();
       t2.start();
       //t1.run();//當做常規方法調用,且 不會發生多線程現象
    }
}

//1、作爲Thread的子類,並重寫run方法。把多線程的業務寫在run方法中
class ThreadDemo extends Thread{  
public ThreadDemo() {}
    public ThreadDemo(String name) {
       super(name);
    } 
    @Override
    public void run() {
       //2、默認實現是super.run();
       for (int i = 0; i < 10; i++) {
           System.out.println(getName()+i);
       }
    }
}

多線程創建2:實現Runnable接口

如果自己的類已經extends另一個類,就無法多繼承,此時,可以實現一個Runnable接口。

常用方法

void run()

      使用實現接口 Runnable 的對象創建一個線程時,啓動該線程將導致在獨立執行的線程中調用對象的 run 方法。 

測試

public class Test2 {
    public static void main(String[] args) {
       MyThread t = new MyThread ();
       //2,構造創建對象,傳入Runnable子類
       Thread target = new Thread(t); 
	   Thread target2 = new Thread(t);
       //開啓線程
       target.start();
       target2.start();
    }
}

//1,實現Runnable接口,重寫run()
class MyThread implements Runnable{
    @Override
    public void run() {
       for (int i = 0; i < 10; i++) {
       System.out.println(Thread.currentThread().getName()+" "+i);
       }
    }
}

注意:可以看到執行順序是亂的,我們已經知道start()方法只是通知操作系統線程就緒,具體什麼時間執行,操作系統來決定,我們JVM已經控制不了了。這就是亂序的原因,也是正常的。


比較
在這裏插入圖片描述

同步鎖:synchronized

把有可能出現問題的代碼包起來,一次只讓一個線程執行。通過sychronized關鍵字實現同步。當多個對象操作共享數據時,可以使用同步鎖解決線程安全問題。

synchronized(對象){
需要同步的代碼;
}

特點
1、 前提1,同步需要兩個或者兩個以上的線程。
2、 前提2,多個線程間必須使用同一個鎖。
3、 同步的缺點是會降低程序的執行效率, 爲了保證線程安全,必須犧牲性能。
4、 可以修飾方法稱爲同步方法,使用的鎖對象是this。
5、 可以修飾代碼塊稱爲同步代碼塊,鎖對象可以任意。


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