一.多線程的介紹

1.線程,進程的基本介紹

一:進程

進程的特性

動態性: 動態產生,動態銷燬,是有生命週期的,是程序執行的動態過程

併發性:在系統上多個進程併發運行

獨立性: 每個進程,系統都會爲一個進程分配獨立的內存空間,進程之間相互獨立,是應用程序構成的獨立單位

異步性: 多個進程併發運行,在處理器進行執行的時候,多個進程之間相互切換,單核處理器下同一時間下只有一個進程中的線程被執行,且各個進程之間都按照非確定效率執行,也就是隨意由操作系統調度執行

結構性:由程序,數據集合,進程控制塊三部分組成

進程由三部分組成:程序,數據集合,進程控制塊

程序:完成所需要的業務,實現需要的功能

數據集合:提供給程序運行執行時所需要的數據

程序控制塊:進程的唯一標識,其包括該進程相關的描述信息和該進程的控制信息

  • 一個進程包括:系統分配獨立的內存空間和至少有一個或者多個線程;數據集合,程序代碼.

二:線程

  1. 最初只有進程沒有線程,進程就是最小執行單元

  2. 在擁有線程後,線程就是最小執行單元

  3. 一個應用程序至少有一個或者多個線程

  4. 每個進程,系統都會爲一個進程分配獨立的內存空間,而線程並不需要,線程共享進程中的數據的,使用系統爲其所在的進程分配的內存空間,因此,創建一個線程的開銷比進程小很多.切換一個線程的開銷比切換一個進程要小很多

  5. 一個進程可以有一個或者多個線程,每一個線程共享進程中的數據的,使用系統爲其所在的進程分配的內存空間

  • 一個標準的線程包括:堆,棧,線程的唯一標識,指令指針,寄存器,

三: 進程與線程的區別

  1. 線程是最小單位的程序執行單位,進程是最小單位的操作系統分配資源單位

  2. 一個進程中可包含多個線程,一個線程是代碼的不同執行路徑

  3. 一個進程中可包含多個線程,而一個線程只能屬於一個進程

  4. 進程之間是相互獨立的,進程之間的通訊需要以通信的方式進行;而一個進程中的線程是可以共享系統爲其所在的進程分配的內存空間,一進程中的線程在其他進程中是不可見的.

  5. 多進程模式下程序更健壯,進程有系統爲其分配的獨立內存空間,一個進程崩潰後,只會對該塊內存區域造成影響,不會影響其他的進程,線程沒有獨立內存空間,代碼的不同執行路徑,所以假如是單進程的話,一個線程出現問題,可能導致整個程序崩潰


2.線程的生命週期

  1. 初始(NEW):創建了一個線程對象,這時,還沒有start().

  2. 運行(RUNNABLE):處於可運行狀態下,等待由操作系統分配時間片執行程序

  3. 阻塞(BLOCKED):線程被阻塞於,當調用了synchronized鎖或者等待解鎖的狀態.

  4. 等待(WAITING):線程被掛起,處於掛起狀態,需要注意的是,與阻塞不同的是被掛起的線程其所指向的代碼其他線程依舊可以執行,而被阻塞的線程其未釋放鎖,其他線程不可執行,使進入等待狀態的有wait(),join(),等操作.

  5. 終止(TERMINATED):表示當前的線程已經執行完成.

一:線程各狀態的相互轉換

在這裏插入圖片描述


3.對線程的操作

一:線程創建的三種方式:

  1. 繼承Thread類創建線程
  2. 實現Runnable接口創建線程
  3. 使用Callable和Future接口創建線程.
    1. 這種方式有的小夥伴可能之前沒有見過,Future其內部是以及實現了Runnable接口,在其實現的run()方法中會調用需要我們實現的Callable,從而實現了在子線程中執行任務的邏輯
    2. 相關API簡介:
      • cancel:取消Future關聯的Callable任務
      • get:返回call()方法的返回值,當call()方法方法體還未執行完會一直阻塞,等執行完才取返回值
      • isDone: Callable任務完成返回ture
      • isCancelled: Callable正常完成前取消,返回ture

二:線程中斷:

stop()方法: 該方法被廢棄,一調用,線程立刻就停止了,此時就會引發相關的線程安全問題

//方法調用展示:
Thread thread1 = new Thread(() -> System.out.println("stop thread"));//此處是lambda表達式
        thread1.start();
        thread1.stop();//該方法已廢棄

三: 線程中的interrupt(),isInterrupt(),interrupted()

interrupt():

  1. 將當前線程的線程 狀態修改爲中斷狀態,本身並不會去中斷正在執行的任務線程
  2. 子線程的執行代碼中,假如包含有Object.wait, Thread.join和Thread.sleep等阻塞函數,在調用interrupt()後會拋出InterruptedException異常,中斷線程,同時會清除線程中斷狀態.
    而這種異常的原因是,因爲這類的阻塞函數內部會不斷檢查當前線程的線程狀態,當判斷到當前線程的線程狀態爲中斷狀態則會拋出InterruptedException異常,而導致了線程中斷
//方法調用展示:
Thread thread1 = new Thread(() -> System.out.println("stop thread"));//此處是lambda表達式
        thread1.start();
        thread1.interrupt();

isInterrupted():

拿取當前線程的狀態,當不處於中斷狀態返回false,當處於中斷狀態返回true.

//方法調用展示:
Thread thread1 = new Thread(() -> System.out.println("stop thread"));//此處是lambda表達式
        thread1.start();
        boolean interrupted = thread1.isInterrupted();//返回當前線程的狀態

interrupted():

interrupted()interrupt() 非常像,但用法是不同的,interrupted():返回線程的上次的中斷狀態,並清除中斷狀態,在此之後查詢中斷狀態interrupted()都會返回false

//方法調用展示:
Thread thread1 = new Thread(() -> System.out.println("stop thread"));//此處是lambda表達式
        thread1.start();
        thread1.interrupted();

中斷可取的方法: 通過interrupt()方法標記該線程爲中斷狀態;在執行邏輯的run()方法中通過isInterrupted()方法判斷,是否被標記爲中斷狀態,當是的時候就跳出執行邏輯

四.線程合併(join):

join:類似在一個方法中去調另一個方法,例如,在A線程執行過程中,假如需要B線程先去執行完成後再繼續執行A,就調用B線程的join方法,在B線程執行完成後將會回到A線程繼續執行

        Thread thread1;
        Thread thread2;
        thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 引用t1線程,等待t1線程執行完
                    thread2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("i'm thread1");
            }
        });
        thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("i'm thread2");
            }
        });
        //兩個線程的啓動順序可以任意,大家可以試下!
        thread1.start();
        thread2.start();
//輸出結果:
System.out.println("i'm thread2");
System.out.println("i'm thread1");

join()源碼分析

join()方法本質上也就是調用了wait()方法:其提供兩個重載方法,一個無參,一個傳入等待時長的方法,在方法體中首先會判斷是否傳入時間,當小於0的時候拋出異常,等於的時候直接wait,大於0時,wait相應時長然後跳出回到原線程.

//join()源碼:
 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
 
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {             //如果時執行的join(0)
            while (isAlive()) {        //如果線程是運行狀態,就會執行下面的等待
                wait(0);               
            }
        } else {                       //如果是執行的join(time)
            while (isAlive()) {               //如果線程時運行狀態
                long delay = millis - now;    
                if (delay <= 0) {
                    break;
                }
                wait(delay);                 //等待delay時間後自動返回繼續執行
                now = System.currentTimeMillis() - base;
            }
        }
 }


4.線程的執行

一:線程的任務調度方式:

Java中,其跑在Linux上,所採用的的任務調度方式是:時間片輪轉搶佔的調度方式,由系統隨機分配時間片給線程執行代碼,在執行很短的時間後就又進入下一次循環

  • 一個任務執行的時間叫做:時間片.

  • 一個任務處於運行執行的狀態叫做:運行狀態.

  • 時間片輪轉搶佔式的調度方式,這樣每一個任務都能夠執行的到,而被暫停的任務處於就緒狀態等待下一個屬於它的時間片到來,由於CPU的切換效率非常高,在多個任務之間迅速相互切換,時間片又非常的短,所以給我的感覺就是 多個任務在同時執行,這也就是我們常說的: 併發

二:線程優先級

作用:
決定了線程按照執行的順序,操作系統爲各個線程都設定了優先級,優先級高的線程更早的執行,優先級低的線程需要等更高優先級的可執行線程執行完成後才能執行.

原理:

線程分爲:

  • IO密集型線程: 頻繁進入等待狀態的線程
  • CPU密集型線程:每次都將所有時間片消耗完的線程,很少等待的線程
    系統也會依據不同形勢下調整線程的優先級,IO密集型線程只會佔有很少的時間片比CPU密集型線程更容易得到線程優先級的提升.不斷進入等待狀態的線程假如一直未被執行,則系統會不斷提高它的優先級,在一定時間和程度後就會被執行.

改變線程優先級的三種方式:

  1. 用戶手動設置
  2. 系統會依據線程進入等待狀態的頻率,提升或者降低線程的優先級
  3. 等待很長時間未被執行的線程被系統提升線程優先級

三:線程餓死

解釋:
當一條線程要去執行的時候,總是會有優先級比它高的線程需要執行,如此循環,該線程一直未得到時間片去執行代碼.

導致線程餓死分析:
在多線程中,大量CPU密集型線程優先級較相對IO密集型較高,這種情況下將會導致IO密集型較高的線程餓死


5.查看線程狀態

方法一:

  1. cmd -> jps : 獲取到當前的線程
  2. jstask …(線程ID)… : 獲取指定線程中的狀態

方法二:假如檢測線程是否鎖死

  1. cmd -> jconsole : 打開控制檯

  2. 在這裏插入圖片描述

  3. 在這裏插入圖片描述

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