【多線程】程序猿進階多線程(三)—— 線程的運行狀態

一、前言

      在上一篇博客中,小編向大家介紹了 線程創建和啓動,簡單的向大家介紹了線程創建和啓動的三種方法:1.繼承Thread類,2.實現Runnable接口,3.使用Future和Task創建。可能大家對線程的初步創建有了一定的瞭解。

      在這篇博客中,小編向大家介紹一下,線程運行起來後,有的狀態。

二、線程的狀態

這裏寫圖片描述

      線程在執行過程中,可以處於下面幾種狀態:

就緒(Runnable):線程準備運行,不一定立馬就能開始執行。
運行中(Running):進程正在執行線程的代碼。
等待中(Waiting):線程處於阻塞的狀態,等待外部的處理結束。
阻塞狀態(Blocked):等待 I/O 操作完成。
同步阻塞(Locking):等待獲取鎖。
死亡(Dead):線程完成了執行。

      下面詳細說明:

  • 就緒狀態 - Runnable

      當我們創建好線程後,Thread t = new Thread();,開始調用t.start();方法後,線程就從初始狀態轉變爲就緒狀態。

      就緒狀態下的線程,在等待操作系統調用,操作系統會根據不同的調度算法來進行調度線程。比如操作系統使用時間片輪轉算法。

  • 運行狀態 - Running

      當操作系統選定要運行的線程後,這個線程就會從就緒狀態轉爲運行狀態。這個時候,運行狀態的線程就會執行我們重寫的run()方法。

  • 阻塞狀態 - blocked

      有的時候,我們需要給線程之間一些緩衝時間,通常使用Sleep()讓子線程和主線程錯開。有的時候我們需要線程按照一定的順序執行,這個時候我們可以使用b.join(),安排在線程b執行完成後再執行。

      所以在sleep()join()調用的過程中,線程會處於阻塞狀態。只有等sleep()join()完成後,線程纔會再次進入就緒狀態,等待cpu調用。

  • 等待狀態 - waiting

      運行的線程執行wait()方法,該線程會釋放佔用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態後,是不能自動喚醒的,必須依靠其他線程調用notify()或notifyAll()方法才能被喚醒

  • 鎖池狀態 - locking

      當執行中的線程進入synchronized同步塊的時候,沒有獲取到鎖的線程,就會進入鎖池狀態,獲取到鎖的線程執行完成後,鎖解除,進入就緒狀態。

  • 死亡狀態 -dead

      線程執行完成,進入死亡狀態。

三、一些知識點

      在上面的線程狀態圖中,裏面有很多方法,可能不是很清楚。下面小編通過幾個問題來對比記憶一下這些方法:

      java多線程中,會有很多問題,比如經典的:

3.1 現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?

【方法一】

      這個簡單的問題,我們可以直接使用join函數完成,join的作用就是,b.join()在b線程執行完成後再執行。這樣就保證了執行順序。

package com.dmsd.thread;

/**
 * Created by Ares on 2018/6/11.
 *
 * 說明:使用join完成 ————t1執行完了,t2執行;t2執行完成後,t3執行。
 *
 * 假設現在有兩個線程A、B。如果在A的run方法中調用B.join(),
 * 表示A需要在B線程上面等待,也就是需要在B線程執行完成之後才能再次執行。
 */
public class join {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 run:"+i);
            }

        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                    t1.join();//表明當前線程需要在t1線程上等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 run:"+i);
            }

        });
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(40);
                    t2.join();//表明當前線程需要在t2線程上等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 run:"+i);
            }

        });
        t1.start();
        t2.start();
        t3.start();
    }
}

【方法二】

      可以說使用join的老鐵,還是比較low的。在我們的juc包( java.util.concurrent)中,利用ExcutorService產生的newSingleThreadExecutor的單一線程池。這個線程池的底層是使用了先進先出的隊列,通過submit依次把線程添加進隊列,然後順序執行。

package com.dmsd.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Ares on 2018/8/14.
 */
public class TestSingleThreadPool  {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Thread t1 = new Thread(new MyThread1());
        Thread t2 = new Thread(new MyThread2());
        Thread t3 = new Thread(new MyThread3());

        executorService.submit(t1);
        executorService.submit(t2);
        executorService.submit(t3);

        executorService.shutdown();

    }
}

class MyThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 1");
    }
}

class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 2");
    }
}

class MyThread3 implements Runnable {
    @Override
    public void run() {
        System.out.println("I am thread 3");
    }
}

      底層中用了newThreadPoolExecutor()創建了線程池,線程池中一個線程,並把線程放到linkedBlockingQueue阻塞隊列中。保證了線程的順序執行。

      源碼:

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.2 Java中sleep和wait的區別 ?

      這個問題也是經常問的問題。

① 這兩個方法來自 不同的類 分別是,sleep來自Thread類,和wait來自Object類。
sleep是Thread的靜態類方法, 誰調用的誰去睡覺,即使在a線程裏調用b的sleep方法,實際上還是a去睡覺, 要讓b線程睡覺要在b的代碼中調用sleep。

② 鎖: 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

      sleep不出讓系統資源;wait是進入線程等待池等待,出讓系統資源,其他線程可以佔用CPU。一般wait不會加時間限制,因爲如果wait線程的運行資源不夠,再出來也沒用,要等待其他線程調用notify/notifyAll喚醒等待池中的所有線程,纔會進入就緒隊列等待OS分配系統資源。sleep(milliseconds)可以用時間指定使它自動喚醒過來,如果時間不到只能調用interrupt()強行打斷。

      Thread.sleep(0)的作用是“觸發操作系統立刻重新進行一次CPU競爭”。

③ 使用範圍:wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用。

   synchronized(x){ 
      x.notify() 
     //或者wait() 
   }

四、小結

      通過線程的狀態,詳細大家可以更加深入的剝開多線程的面紗,對多線程的理解也會更加的深刻。

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