1. 程式人生 > >執行緒基礎(1)

執行緒基礎(1)

概念

執行程式會建立一個程序。是OS排程的最小單元是執行緒(輕量級程序)。

普通的java程式包含的執行緒:

  • 11:Monitor Ctrl-Break //監聽中斷訊號
  • 5:Attach Listener //獲取記憶體dump,執行緒dump
  • 4:Signal Dispatcher //z將訊號分給jvm的執行緒
  • 3:Finalizer //呼叫物件的finalizer 方法
  • 2:Reference Handler //清除Reference
  • 1:main //程式的主入口
public class ShowMainThread1 {

    /**
     * 這裡啟了一個執行緒,main 執行緒,但是後臺jvm 其實一共啟了 6 個執行緒
     *
     * @param args
     */
    public static void main(String[] args) {
        //java虛擬機器的執行緒管理介面
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //獲取執行緒資訊的方法
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
        }
    }


}

為什麼要用執行緒?

  • 充分利用多處理核心;
  • 更快的響應時間(使用者訂單的場景,傳送郵件等部分可由其他執行緒執行)

啟動執行緒和退出執行緒

建立執行緒的方法

extends Thread
implements Runnable
啟動執行緒:threadl類的start()

退出執行緒:

  • run()方法執行完成;
  • 丟擲一個未處理的異常導致執行緒的提前結束
public class HowStartThread2 {

    /**
     * 申明一個執行緒
     */
    private static class TestThread extends Thread {
        @Override
        public void run() {
            System.out.println("TestThread is runing");

        }
    }


    /**
     * 申明一個執行緒
     */
    private static class TestRunable implements Runnable {

        @Override
        public void run() {
            System.out.println("TestRunable is runing");
        }
    }


    public static void main(String[] args) {
        Thread t1 = new TestThread();
        Thread t2 = new Thread(new TestRunable());
        t1.start();
        t2.start();

    }

}

取消和中斷

不安全的取消

  • 單獨使用一個取消標誌位.來取消執行緒是不安全的
  • Stop(),suspend(),resume()是過期的api,很大的副作用,容易導致死鎖或者資料不一致

如何安全的終止執行緒

使用執行緒的中斷 :
interrupt(),在一個執行緒中呼叫另一個執行緒的interrupt()方法,即會向那個執行緒發出訊號——執行緒中斷狀態已被設定。至於那個執行緒何去何從,由具體的程式碼實現決定。
isInterrupted(),用來判斷當前執行緒的中斷狀態(true or false)。
interrupted()是個Thread的static方法,用來將中斷標誌位復位為false

由上面的中斷機制可知Java裡是沒有搶佔式任務(強制關閉執行緒),只有協作式任務。

為何要用中斷,執行緒處於阻塞(如呼叫了java的sleep,wait等等方法時)的時候,是不會理會我們自己設定的取消標誌位的,但是這些阻塞方法都會檢查執行緒的中斷標誌位。

// 安全的中斷執行緒
public class SafeInterrupt4 implements Runnable {
    /**
     * 執行緒中斷標誌位
     */
    private volatile boolean on = true;
    private long i = 0;

    @Override
    public void run() {

        // isInterrupted 執行緒檢查自己的中斷標誌位,判斷執行緒是否已被髮送過中斷請求
        while (on && !Thread.currentThread().isInterrupted()) {
            i++;
            try {
                System.out.println("執行緒休眠:===>>" + i);
                
                 //丟擲中斷異常的阻塞方法,丟擲異常後,中斷標誌位改成false
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//重新設定一下
                e.printStackTrace();
            }
        }
        System.out.println("TestRunable is runing :" + i);
    }

    public void cancel() {
        on = false;
        // interrupt 中斷執行緒,本質是將執行緒的中斷標誌位設為 true
        Thread.currentThread().interrupt();
        System.out.println("設定執行緒執行結束標誌位");
    }

    public static void main(String[] args) throws InterruptedException {
        SafeInterrupt4 safeInterrupt4 = new SafeInterrupt4();

        Thread thread = new Thread(safeInterrupt4);

        thread.start();

        for (int i = 0; i < 100; i++) {
            if (i == 80) {
                safeInterrupt4.cancel();
                System.out.println("結束執行緒=========>>" + i);
            }
        }
    }
}

處理不可中斷的阻塞

IO通訊 inputstream read/write等阻塞方法,不會理會中斷,而關閉底層的套接字socket.close()會丟擲socketException
NIO: selector.select()會阻塞,呼叫selector的wakeup和close方法會丟擲ClosedSelectorException
死鎖狀態不響應中斷的請求,這個必須重啟程式,修改錯誤。

如何讓我們的程式碼既可以響應普通的中斷,又可以關閉底層的套接字呢?

覆蓋執行緒的interrupt方法,在處理套接字異常時,再在 finally 方法中 用super.interrupt()自行中斷執行緒

執行緒關態

  • 新建立 執行緒被建立,但是沒有呼叫start方法
  • 可執行(RUNNABLE) 執行狀態,由cpu決定是不是正在執行
  • 被阻塞(BLOCKING) 阻塞,執行緒被阻塞於鎖
  • 等待/計時等待(WAITING) 等待某些條件成熟
  • 被終止 執行緒執行完畢

執行緒的優先順序

成員變數priority控制優先順序,範圍1-10之間,數字越高優先順序越高,預設為5,建立執行緒時setPriotity()可以設定優先順序,不要指望他發揮作用。

Daemon執行緒

守護型執行緒(如GC執行緒),程式裡沒有非Daemon執行緒時,java程式就會退出。一般用不上,也不建議我們平時開發時使用,因為Try/Finally裡的程式碼不一定執行的。

常用方法深入理解

run()和start()
run就是一個普通的方法,跟其他類的例項方法沒有任何區別。

Sleep
不會釋放鎖,所以我們在用sleep時,要把sleep放在同步程式碼塊的外面。

yield()
當前執行緒出讓cpu佔有權,當前執行緒變成了可執行狀態,下一時刻仍然可能被cpu選中,不會釋放鎖。

  • yield是一個靜態的原生(native)方法
  • yield告訴當前正在執行的執行緒把執行機會交給執行緒池中擁有相同優先順序的執行緒。
  • yield不能保證使得當前正在執行的執行緒迅速轉換到可執行的狀態
  • 它僅能使一個執行緒從執行狀態轉到可執行狀態,而不是等待或阻塞狀態

wait()和 notify()/notiyfAll()
呼叫以前,當前執行緒必須要持有鎖,呼叫了wait() notify()/notiyfAll()會釋放鎖。

等待通知機制:
執行緒 A呼叫了物件O的wait方法進入等待狀態,執行緒 B呼叫了物件O的notify方法進行喚醒,喚醒的是在物件O上wait的執行緒(比如執行緒A)
notify() 喚醒一個執行緒,喚醒哪一個完全看cpu的心情(謹慎使用)
notiyfAll() 所有在物件O上wait的執行緒全部喚醒(應該用notiyfAll())

參考: