java併發程式設計 | 執行緒詳解

叫我明羽發表於2019-04-08

個人網站:chenmingyu.top/concurrent-…

程式與執行緒

程式:作業系統在執行一個程式的時候就會為其建立一個程式(比如一個java程式),程式是資源分配的最小單位,一個程式包含多個執行緒

執行緒:執行緒是cpu排程的最小單位,每個執行緒擁有各自的計數器,對戰和區域性變數等屬性,並且能過訪問共享的記憶體變數

執行緒的狀態

java執行緒的生命週期總共包括6個階段:

  1. 初始狀態:執行緒被建立,但是還沒有呼叫start()方法
  2. 執行狀態:java中將就緒狀態和執行狀態統稱為執行狀態
  3. 阻塞狀態:執行緒阻塞,執行緒等待進入synchronized修飾的程式碼塊或方法
  4. 等待狀態:執行緒進入等待狀態,需要呼叫notify()notifyAll()進行喚醒
  5. 超時等待狀態:執行緒進入等待狀態,在指定時間後自行返回
  6. 終止狀態:執行緒執行完畢

在某一時刻,執行緒只能處於其中的一個狀態

java併發程式設計 | 執行緒詳解

執行緒初始化後,呼叫start()方法變為執行狀態,呼叫wait()join()等方法,執行緒由執行狀態變為等待狀態,呼叫notify()notifyAll()等方法,執行緒由等待狀態變成執行狀態,超時等待狀態就是在等待狀態基礎上加了時間限制,超過規定時間,自動更改為執行狀態,當需要執行同步方法時,如果沒有獲得鎖,這時執行緒狀態就變為阻塞狀態,直到獲取到鎖,變為執行狀態,當執行完執行緒的run()方法後,執行緒變為終止狀態

建立執行緒

建立執行緒有三種方式

  1. 繼承Thread
  2. 實現Runnable介面
  3. 實現Callable介面

繼承Thread

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:13
 * @description: 繼承Thread類
 */
public class ThreadTest extends Thread{

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(this.getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Thread thread = new ThreadTest();
        thread.start();
    }
}
複製程式碼
實現Runnable介面
/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:18
 * @description: 實現Runnable介面
 */
public class RunnableTest implements Runnable {

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Runnable runnable = new RunnableTest();
        new Thread(runnable,"RunnableTest").start();
    }
}
複製程式碼
實現Callable介面
/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:23
 * @description: 實現Callable介面
 */
public class CallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
        return -1;
    }

    public static void main(String[] args) throws Exception {
        Callable callable = new CallableTest();
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask,"future").start();
        System.out.println("result:"+futureTask.get());
    }
}
複製程式碼
執行緒的暫停,恢復,停止

不安全的執行緒暫停,恢復,停止操作

Thread提供的過期方法可以實現對執行緒進行暫停suspend(),恢復resume(),停止stop()的操作

例:建立一個執行緒,run()中迴圈輸出當前時間,在main()方法中對新建執行緒進行暫停,恢復,停止的操作

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:51
 * @description: 執行緒的暫停,恢復,停止
 */
public class OperationThread implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now());
            }catch (InterruptedException e){
                System.err.println(e.getMessage());
            }
        }
    }

    public static void main(String[] args) throws Exception{
        Runnable runnable = new OperationThread();
        Thread thread = new Thread(runnable,"operationThread");
        /**
         * 啟動,輸出當前時間
         */
        thread.start();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 執行緒暫停,不在輸出當前時間
         */
        System.out.println("此處暫停:"+LocalTime.now());
        thread.suspend();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 執行緒恢復,繼續輸出當前時間
         */
        System.out.println("此處恢復:"+LocalTime.now());
        thread.resume();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 執行緒停止,不在輸出當前時間
         */
        thread.stop();
        System.out.println("此處停止:"+LocalTime.now());
        TimeUnit.SECONDS.sleep(3L);
    }
}
複製程式碼

輸出

java併發程式設計 | 執行緒詳解
因為是過期方法,所以不推薦使用,使用suspend()方法後,執行緒不會釋放已經佔有的資源,就進入睡眠狀態,容易引發死鎖問題,而使用stop()方法終結一個執行緒是不會保證執行緒的資源正常釋放的,可能會導致程式異常

安全的執行緒暫停,恢復,停止操作

執行緒安全的暫停,恢復操作可以使用等待/通知機制代替,安全的停止操作可以用執行緒是否被中斷進行判斷

安全的執行緒暫停,恢復(等待/通知機制)

相關方法:

方法名 描述
notify() 通知一個在物件上等待的執行緒,使其重wait()方法中返回,前提是該執行緒獲得了物件的鎖
notifyAll() 通知所有等待在該物件上的執行緒
wait() 呼叫該方法執行緒進入等待狀態,只有等待另外執行緒的通知或被中斷才會返回,呼叫該方法會釋放物件的鎖
wait(long) 超時等待一段時間(毫秒),如果超過時間就返回
wait(long,int) 對於超時時間耕細粒度的控制,可以達到納秒

例:建立一個名為waitThread的執行緒,在run()方法,使用中使用synchronized進行加鎖,以變數flag為條件進行while迴圈,在迴圈中呼叫LOCK.wait()方法,此時會釋放物件鎖,由main()方法獲得鎖,呼叫LOCK.notify()方法通知LOCK物件上等待的waitThread執行緒,將其置為阻塞狀態,並將變數flag置為true,當waitThread執行緒再次獲取物件鎖之後繼續執行餘下程式碼

/**
 * @author: chenmingyu
 * @date: 2019/4/8 20:00
 * @description: wait/notify
 */
public class WaitNotifyTest {

    private static Object LOCK = new Object();
    private static Boolean FLAG = Boolean.TRUE;


    public static void main(String[] args) throws InterruptedException{
        Runnable r = new WaitThread();
        new Thread(r,"waitThread").start();
        TimeUnit.SECONDS.sleep(1L);
        synchronized (LOCK){
            System.out.println(Thread.currentThread().getName()+"喚醒waitThread執行緒:"+LocalTime.now());
            /**
             * 執行緒狀態由等待狀態變為阻塞狀態
             */
            LOCK.notify();
            /**
             * 只有當前執行緒釋放物件鎖,waitThread獲取到LOCK物件的鎖之後才會從wait()方法中返回
             */
            TimeUnit.SECONDS.sleep(2L);
            FLAG = Boolean.FALSE;
        }
    }

    public static class WaitThread implements Runnable {
        @Override
        public void run() {
            /**
             * 加鎖
             */
            synchronized (LOCK){
                while (FLAG){
                    try {
                        System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now());
                        /**
                         * 執行緒狀態變為等待狀態
                         */
                        LOCK.wait();
                        /**
                         * 再次獲得物件鎖之後,才會執行
                         */
                        System.out.println(Thread.currentThread().getName()+"被喚醒:"+LocalTime.now());
                    }catch (InterruptedException e){
                        System.err.println(e.getMessage());
                    }
                }
            }
            System.out.println(Thread.currentThread().getName()+"即將停止:"+LocalTime.now());
        }
    }
}
複製程式碼

輸出

java併發程式設計 | 執行緒詳解
可以看到在mian執行緒呼叫LOCK.notify()方法後,沉睡了2s才釋放物件鎖,waitThread執行緒在獲得物件鎖之後執行餘下程式碼

安全的執行緒停止操作(中斷標識)

執行緒的安全停止操作是利用執行緒的中斷標識來實現,執行緒的中斷屬性表示一個執行彙總的執行緒是否被其他執行緒進行了中斷操作,其他執行緒通過呼叫該執行緒的interrupt()方法對其進行中斷操作,而該執行緒通過檢查自身是否被中斷來進行響應,當一個執行緒被中斷可以使用Thread.interrupted()方法對當前執行緒的中斷標識位進行復位

例:新建一個執行緒,run方法中使用Thread.currentThread().isInterrupted()是否中斷作為判斷條件,在主執行緒中使用thread.interrupt()方法對子執行緒進行中斷操作,用來達到終止執行緒的操作,這種方式會讓子執行緒可以去清理資源或一些別的操作,而使用stop()方法則會會直接終止執行緒

/**
 * @author: chenmingyu
 * @date: 2019/4/8 20:47
 * @description: 中斷
 */
public class InterruptTest {
    
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new StopThread();
        Thread thread = new Thread(r,"stopThread");
        thread.start();
        TimeUnit.SECONDS.sleep(1L);
        System.out.println(Thread.currentThread().getName()+"對stopThread執行緒進行中斷:"+LocalTime.now());
        thread.interrupt();
    }

    public static class StopThread implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now());
            }
            System.out.println(Thread.currentThread().getName()+"停止:"+LocalTime.now());
        }
    }
}
複製程式碼

未完待續...

參考:java併發程式設計的藝術

相關文章