01-多線程-基礎

線程的生命週期

線程的生命週期

線程共包括一下5種狀態java

  • 新建狀態(New):線程對象被建立之後,就進入了新建建立,例如:Thread thread = new Thread()。
  • 就緒狀態(Runnable):也被稱爲:可執行的狀態,線程對象被建立之後,其餘線程調用了該對象的start()方法,從而來啓動該線程,例如:thread.start(),處於就緒狀態的線程能夠被CPU調用
  • 運行狀態(Running):線程獲取cpu權限進行執行,須要注意的是:線程只能從就緒狀態進入運行狀態
  • 阻塞狀態(Blocked):阻塞狀態是線程由於某種緣由放棄了cpu的使用權,暫時中止運行,直到線程進入就緒狀態,纔有機會轉到運行狀態,阻塞分爲三種:
    • 等待阻塞 :經過調用線程的wait()方法,讓線程等待某工做的完成
    • 同步阻塞 :線程在獲取synchornized同步鎖失敗(由於鎖被其餘線程所佔用),他會進入同步阻塞狀態
    • 其餘阻塞 :經過調用線程的sleep()或者join()或者發出了I/O請求時,線程會進入到阻塞的狀態,當sleep()狀態超時,join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。
  • 死亡狀態(Dead): 線程執行完課或者由於異常退出了run()方法,該線程結束生命週期

線程的實現方式

經常使用的實現方式
  • 繼承抽象類Thread 重寫run()方法
  • 實現接口Runnable
線程池的實現
  • 還能夠經過java.util.concurrent包中的線程池來實現多線程。

Thread中start()和run()的區別

  • start():它的做用就是啓動一個新線程,新線程會執行相應的run()方法,start()不能被重複調用
  • run():和普通的成員方法同樣,能夠被重複的調用,單獨調用run()的話,會在當前線程中執行run(),並且並不會啓動新線程
源碼分析 基於jdk8
//工具的Java線程狀態, 初始化表示線程'還沒有啓動'
    private volatile int threadStatus = 0;
    
     
     //這個線程的組
    private ThreadGroup group;
    
    public synchronized void start() {
          //若是線程不是"就緒狀態",則拋出異常!
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

       //將線程添加到ThreadGroup中
       //通知組該線程即將啓動,這樣它就能夠添加到組的線程列表中,而且該組的未啓動計數能夠遞減
        group.add(this);

        boolean started = false;
        try {
        //經過該方法啓動線程
            start0();
            //設置標記
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
複製代碼

start()其實是經過本地方法start0()啓動線程的。而start0()會新運行一個線程,新線程會調用run()方法。安全

run()方法分析
/* What will be run. */
    private Runnable target;

   public void run() {
        if (target != null) {
            target.run();
        }
    }
複製代碼

target是一個Runnable對象。run()就是直接調用Thread線程的Runnable成員的run()方法,並不會新建一個線程。bash

synchronized

原理
  • 在java中,每個對象有且僅有一個同步鎖,這就意味着,同步鎖是依賴於對象存在的
  • 當咱們調用某對象的synchronized方法時,就獲取了該對象的同步鎖
  • 不一樣線程對同步鎖的訪問是互斥的
synchronized方法 和 synchronized代碼塊

「synchronized方法」是用synchronized修飾方法,而 「synchronized代碼塊」則是用synchronized修飾代碼塊。多線程

synchronized 實例鎖 和 全局鎖
  • 實例鎖 -- 鎖在某一個實例對象上。若是該類是單例,那麼該鎖也具備全局鎖的概念。 實例鎖對應的就是synchronized關鍵字。
  • 該鎖針對的是類,不管實例多少個對象,那麼線程都共享該鎖。全局鎖對應的就是staticsynchronized(或者是鎖在該類的class或者classloader對象上)。

線程的等待和喚醒

wait(), notify(), notifyAll()等方法介紹
  • 三個方法都定義在Object對象中
  • wait():讓當前線程進入等待狀態,即阻塞狀態,同時會釋放當前線程持有的鎖,可使用 notify() 方法或 notifyAll() 方法喚醒線程,wait()的做用是讓「當前線程」等待,而「當前線程」是指正在cpu上運行的線程!
  • notify():喚醒單個線程,喚醒當前對象上的等待線程
  • notifyAll():喚醒當前對象上的全部等待線程
爲何notify(), wait()等函數定義在Object中,而不是Thread中
  • Object中的wait(), notify()等函數,和synchronized同樣,會對「對象的同步鎖」進行操做。
  • wait()會使「當前線程」等待,由於線程進入等待狀態,因此線程應該釋放它鎖持有的「同步鎖」,不然其它線程獲取不到該「同步鎖」而沒法運行!
  • 線程調用wait()以後,會釋放它鎖持有的「同步鎖」;並且,根據前面的介紹,咱們知道:等待線程能夠被notify()或notifyAll()喚醒。如今,請思考一個問題:notify()是依據什麼喚醒等待線程的?或者說,wait()等待線程和notify()之間是經過什麼關聯起來的?答案是:依據「對象的同步鎖」。
  • 負責喚醒等待線程的那個線程(咱們稱爲「喚醒線程」),它只有在獲取「該對象的同步鎖」(這裏的同步鎖必須和等待線程的同步鎖是同一個),而且調用notify()或notifyAll()方法以後,才能喚醒等待線程。雖然,等待線程被喚醒;可是,它不能馬上執行,由於喚醒線程還持有「該對象的同步鎖」。必須等到喚醒線程釋放了「對象的同步鎖」以後,等待線程才能獲取到「對象的同步鎖」進而繼續運行。
  • notify(), wait()依賴於「同步鎖」,而「同步鎖」是對象鎖持有,而且每一個對象有且僅有一個!這就是爲何notify(), wait()等函數定義在Object類,而不是Thread類中的緣由。

線程的讓步

yield()介紹
  • yield()的做用是讓步,讓當前線程由運行狀態進入到就緒狀態,從而讓其餘具備相同優先級的等待線程獲取執行權
  • 可是,並不能保證當前線程調用yield()以後,其餘具備相同優先級的線程就必定能獲取執行權;也有多是當前線程又進入到運行狀態繼續運行
yield() 與 wait()的比較
  • wait()是讓線程由「運行狀態」進入到「等待(阻塞)狀態」,yield()是讓線程由「運行狀態」進入到「就緒狀態」。
  • wait()是會線程釋放它所持有對象的同步鎖,而yield()方法不會釋放鎖。

線程的休眠

sleep()介紹
  • sleep() 定義在Thread.java中。
  • sleep() 的做用是讓當前線程休眠,即當前線程會從「運行狀態」進入到「休眠(阻塞)狀態」。
  • sleep()會指定休眠時間,線程休眠的時間會大於/等於該休眠時間;在線程從新被喚醒時,它會由「阻塞狀態」變成「就緒狀態」,從而等待cpu的調度執行。
sleep() 與 wait()的比較
  • wait()的做用是讓當前線程由「運行狀態」進入「等待(阻塞)狀態」的同時,也會釋放同步鎖。
  • sleep()的做用是也是讓當前線程由「運行狀態」進入到「休眠(阻塞)狀態」。 可是,wait()會釋放對象的同步鎖,而sleep()則不會釋放鎖。

線程的禮讓

join()介紹
  • join() 定義在Thread.java中。
  • join() 的做用:讓「主線程」等待「子線程」結束以後才能繼續運行

線程的終止方式和interrupt()

interrupt()說明
  • 做用:中斷本線程
  • 本線程中斷本身是被容許的,其餘線程調用本線程的interrupt()方法時,會經過checkAccess()檢查權限。這有可能拋出SecurityException異常。
終止線程的方式
  • Thread中的stop()和suspend()方法,因爲固有的不安全性,已經建議再也不使用!
終止處於「阻塞狀態」的線程
  • 一般,咱們經過「中斷」方式終止處於「阻塞狀態」的線程。
  • 當線程因爲被調用了sleep(), wait(), join()等方法而進入阻塞狀態;若此時調用線程的interrupt()將線程的中斷標記設爲true
  • 處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException異常。將InterruptedException放在適當的爲止就能終止線程,
@Override
public void run() {
    while (true) {
        try {
            // 執行任務...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)循環體內。
            // 當線程產生了InterruptedException異常時,while(true)仍能繼續運行!須要手動退出
            break;
        }
    }
}
複製代碼
終止處於「運行狀態」的線程
  • 一般,咱們經過「標記」方式終止處於「運行狀態」的線程。其中,包括「中斷標記」和「額外添加標記」。
終止線程的示例
  • interrupt()經常被用來終止「阻塞狀態」線程。參考下面示例:
// Demo1.java的源碼
class MyThread extends Thread {
    
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {  
            int i=0;
            while (!isInterrupted()) {
                Thread.sleep(100); // 休眠100ms
                i++;
                System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
            }
        } catch (InterruptedException e) {  
            System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {  
        try {  
            Thread t1 = new MyThread("t1");  // 新建「線程t1」
            System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  

            t1.start();                      // 啓動「線程t1」
            System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  

            // 主線程休眠300ms,而後主線程給t1發「中斷」指令。
            Thread.sleep(300);
            t1.interrupt();
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

            // 主線程休眠300ms,而後查看t1的狀態。
            Thread.sleep(300);
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }
    } 
}
複製代碼
interrupted() 和 isInterrupted()的區別
  • interrupted() 和 isInterrupted()都可以用於檢測對象的「中斷標記」。
  • interrupted()除了返回中斷標記以外,它還會清除中斷標記(即將中斷標記設爲false);而isInterrupted()僅僅返回中斷標記。

線程的優先級和守護線程

線程優先級的介紹
  • java 中的線程優先級的範圍是1~10,默認的優先級是5。
  • 「高優先級線程」會優先於「低優先級線程」執行。
  • java 中有兩種線程:用戶線程和守護線程。能夠經過isDaemon()方法來區別它們
  • 若是返回false,則說明該線程是「用戶線程」;不然就是「守護線程」。
  • 用戶線程通常用戶執行用戶級任務,而守護線程也就是「後臺線程」,通常用來執行後臺任務
  • Java虛擬機在「用戶線程」都結束後會後退出。
//設置該線程的優先級
thread.setPriority(1);
複製代碼
相關文章
相關標籤/搜索