Hello,Thread

Java學習錄發表於2019-03-22

建立執行緒的三種方法,執行緒的生命週期,sleep,yield,join,wait 和notify,執行緒組,守護執行緒,執行緒的優先順序

如何建立執行緒

Java 中建立執行緒的方法有三種:

  1. 繼承 Thread 類建立執行緒

新建一個類繼承 Thread 類,並重寫 Thread 類的 run() 方法。
建立 Thread 子類的例項。
呼叫該子類例項的 start() 方法啟動該執行緒。

程式碼舉例如下:

123456789101112複製程式碼
public class HelloThread1 {    static class ThreadDemo extends Thread {        @Override        public void run() {            System.out.println("Hello Thread");        }    }    public static void main(String[] args) {        ThreadDemo threadDemo = new ThreadDemo();        threadDemo.start();    }}複製程式碼

2.實現 Runnable 介面建立執行緒:

建立一個類實現 Runnable 介面,並重寫該介面的 run() 方法。
建立該實現類的例項。
將該例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。
呼叫該 Thread 執行緒物件的 start() 方法。

程式碼舉例如下:

123456789101112複製程式碼
public class HelloThread1 {    static class ThreadDemo extends Thread {        @Override        public void run() {            System.out.println("Hello Thread");        }    }    public static void main(String[] args) {        ThreadDemo threadDemo = new ThreadDemo();        threadDemo.start();    }}複製程式碼
  1. 使用 Callable 和 FutureTask 建立執行緒:

建立一個類實現 Callable 介面,並重寫 call() 方法。
建立該 Callable 介面實現類的例項。
將 Callable 的實現類例項傳入 FutureTask(Callable callable) 構造方法中建立 FutureTask 例項。
將 FutureTask 例項傳入 Thread(Runnable r) 構造方法中建立 Thread 例項。
呼叫該 Thread 執行緒物件的 start() 方法。
呼叫 FutureTask 例項物件的 get() 方法獲取返回值。

程式碼舉例如下:

1234567891011121314151617181920複製程式碼
public class HelloThread3 {    static class ThreadDemo implements Callable<String> {        @Override        public String call() {            System.out.println("Hello Thread");            return "Callable return value";        }    }    public static void main(String[] args) {        ThreadDemo threadDemo = new ThreadDemo();        FutureTask<String> futureTask = new FutureTask<String>(threadDemo);        Thread thread = new Thread(futureTask);        thread.start();        try {            System.out.println(futureTask.get());        } catch (Exception e) {            e.printStackTrace();        }    }}複製程式碼


執行緒的生命週期

關於Java中執行緒的生命週期,首先看一下下面這張圖:

上圖中基本上囊括了Java中多執行緒各重要知識點。掌握了上圖中的各知識點,Java中的多執行緒也就基本上掌握了。


執行緒的基本狀態

新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當呼叫方法t.start()時,執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行;同樣還有幾種情況會進行就緒狀態,請參見上圖。

執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:請參見上圖。

死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。


執行緒的基本操作

sleep:使當前執行緒休眠指定毫秒

yield:使當前執行緒讓出CPU,從執行狀態轉到可執行狀態。注意僅僅是讓出,讓出之後也會加入到搶佔資源的隊伍中。

join:把指定的執行緒加入到當前執行緒,比如線上程B中呼叫了執行緒A的Join()方法,直到執行緒A執行完畢後,才會繼續執行執行緒B

執行緒停止:
Thread本身提供了一個stop方法,但是這個不推薦使用。因為使用stop的時候會暴力終止執行緒,從而造成資料不一致。
優雅的停止執行緒的程式碼舉例如下:

123456789101112131415161718192021222324252627複製程式碼
public class StopThread {    static class ThreadDemo extends Thread {        volatile boolean stopMe = false;        public void stopMe(){            this.stopMe=true;        }        @Override        public void run() {            while (true) {                if (stopMe) {                    System.out.println("程式結束");                    break;                }                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) {        ThreadDemo threadDemo = new ThreadDemo();        threadDemo.start();        threadDemo.stopMe();    }}複製程式碼

以一個volatile修飾的變數stopMe來控制執行緒的停止。

執行緒中斷:

執行緒中斷的相關方法分別是這三個

123複製程式碼
public void interrupt() ; //中斷執行緒public boolean isInterrupted(); //判斷執行緒是否被中斷public static boolean interrupted(); //判斷執行緒是否被中斷,並清除當前中斷狀態複製程式碼

執行緒中斷的程式碼舉例如下:

1234567891011121314151617181920212223複製程式碼
public class InterruptThread {    static class ThreadDemo extends Thread {        @Override        public void run() {            while (true) {                if (Thread.currentThread().isInterrupted()) {                    System.out.println("程式結束");                    break;                }                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) {        ThreadDemo threadDemo = new ThreadDemo();        threadDemo.start();        threadDemo.interrupt();    }}複製程式碼

wait 和notify
這兩個方法是JDK為了支援多執行緒之間的協作而提供的。
當線上程A中呼叫了obj.wait()方法時,執行緒A會停止執行進入等待狀態。直到其他執行緒呼叫obj.notify()時才會進入阻塞狀態繼而等待獲取鎖。
請看下方示例程式碼

12345678910111213141516171819202122232425262728293031323334353637383940414243444546複製程式碼
public class WaitNotifyThread {    public static Object obj = new Object();    static class WaitThreadDemo extends Thread {        @Override        public void run() {            synchronized (obj) {                try {                    System.out.println("WaitThread wait,time=" + System.currentTimeMillis());                    obj.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("WaitThread end,time=" + System.currentTimeMillis());            }        }    }    static class NotifyThreadDemo extends Thread {        @Override        public void run() {            synchronized (obj) {                System.out.println("NotifyThread notify,time=" + System.currentTimeMillis());                obj.notify();                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("NotifyThread end,time=" + System.currentTimeMillis());            }        }    }    public static void main(String[] args) {        WaitThreadDemo waitThreadDemo = new WaitThreadDemo();        NotifyThreadDemo notifyThreadDemo = new NotifyThreadDemo();        waitThreadDemo.start();        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        notifyThreadDemo.start();    }}複製程式碼

在上方的程式碼中,Wait執行緒會首先獲取到obj的鎖,當它執行wait方法時就會釋放obj的鎖並進入等待狀態。這個時候Notify執行緒可以獲取到obj的鎖,並且喚醒Wait執行緒,但是因為此時Notify執行緒是睡眠了2秒鐘之後才釋放的obj的鎖,所以Wait執行緒獲取鎖的時候Notify執行緒已經執行完畢了。
此程式的執行結果:

1234複製程式碼
WaitThread wait,time=1553088237753NotifyThread notify,time=1553088237862NotifyThread end,time=1553088239867WaitThread end,time=1553088239867複製程式碼

suspen和resume
它們兩個的功能是掛起執行緒和繼續執行,被suspen掛起的執行緒必須被resume喚醒才可以繼續執行。乍看起來 可以實現wait和notify的功能,不過我可不推薦你使用它們,和wait之後會釋放鎖不同,suspen掛起之後依然會持有鎖,這個可就非常危險了。

執行緒組
在一個系統中如果執行緒數量眾多而又功能比較一致,就可以把這些執行緒放到一個執行緒組裡。
執行緒組示例程式碼:

12345678910111213141516171819202122複製程式碼
public class ThreadGroupDemo {    static class ThreadDemo extends Thread {        @Override        public void run() {            while (true){                System.out.println("I am "+Thread.currentThread().getThreadGroup().getName()+"-"+Thread.currentThread().getName());                try {                    sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) {        ThreadGroup threadGroup=new ThreadGroup("groupDemo");        Thread t1=new Thread(threadGroup,new ThreadDemo(),"t1");        Thread t2=new Thread(threadGroup,new ThreadDemo(),"t2");        t1.start();        t2.start();    }}複製程式碼

守護執行緒
線上程的世界裡,由我們自己建立的執行緒叫使用者執行緒。而一些系統建立的執行緒,如垃圾回收執行緒等被稱之為守護執行緒,如果想要把一個使用者執行緒設定為守護執行緒可以線上程的呼叫執行緒的start方法前設定執行緒的daemon屬性為true;

1複製程式碼
t1.setDaemon(true);複製程式碼

當一個程式中只有守護執行緒的時候這個程式也就要結束了。

執行緒的優先順序

Java中的執行緒可以有自己的優先順序,優先順序高的執行緒在進行資源搶佔的時候往往會更有優勢。執行緒飢餓現象就是由於執行緒的優先順序低無法搶佔資源而引起的。
在Java中執行緒的優先順序可以設定的範圍是1到10之間,數字越大優先順序越高。Java執行緒建立預設的優先順序是5,我們可以執行緒start之前通過如下方式設定執行緒的優先順序。

1複製程式碼
t1.setPriority(1);複製程式碼

本文所有原始碼github.com/shiyujun

Hello,Thread