1. 程式人生 > >Java多執行緒之基礎篇(二)

Java多執行緒之基礎篇(二)

上一篇介紹了Java多執行緒的基礎概念和synchronized關鍵字,這篇繼續介紹Java多執行緒的其他關鍵字和重要的方法。

一、volatile關鍵字

1.1 Java記憶體模型

簡單瞭解一下Java記憶體模型(JMM,Java Memory Model)
Java記憶體模型圖

所有的變數都是儲存在主記憶體中,每個執行緒都是獨立的工作記憶體,裡面儲存該執行緒使用到的變數的副本。執行緒對共享共享變數的所有操作必須在自己的工作記憶體,不同執行緒之間無法直接訪問其他執行緒工作記憶體中的變數,執行緒間變數值傳遞需要通過主記憶體來完成。例如,執行緒1對共享變數的修改,要想被執行緒2及時看到,必須經歷如下兩個過程:
(1)把工作記憶體1中更新過的變數重新整理到主記憶體中。
(2)將主記憶體中最新的共享變數的值更新到執行緒2中。

1.2 基本概念

可見性:
指執行緒之間的可見性,一個執行緒修改狀態對另一個執行緒是可見的。volatile修飾的變數就具有可見性。volatile修飾的變數不允許執行緒內部快取和重排序,即直接修改記憶體。所以對其他執行緒是可見的。但是,volatile修飾的變數不具有原子性。比如 volatile int a = 0;之後有一個操作 a++;這個變數a具有可見性,但是a++ 依然是一個非原子操作,也就是這個操作同樣存線上程安全問題。在Java中volatile、synchronized和final具有可見性。

原子性:
JVM中執行的最小單位,具有不可分割性。比如,int a =0;(a非long和double型別) 這個操作是不可分割的,那麼我們說這個操作時原子操作。但是,a++;這個實際上就是a=a+1;是可分割的,所以不具有原子操作。非原子操作都會存線上程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那麼我們稱它具有原子性。Java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。

有序性:
Java語言提供volatile和synchronized兩個關鍵字來保證執行緒之間操作的有序性,volatile是因為其本身有禁止指令重排的,synchronized是由一個變數在同一時刻只允許一個執行緒對齊進行操作,也就決定了持有同一個物件鎖的兩個同步塊只能序列執行。

1.3 Volatile原理

Java語言提供了一種稍微同步機制,即volatile變數,用來確保將變數的更新操作通知其他執行緒。當把變數宣告為volatile型別後,編譯器與執行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作儀器重排序。volatile變數不會被快取在暫存器或者其他處理器不可見的地方,因此在讀取volatile型別變數是總會返回最新寫入的值。
在訪問volatile變數是不會執行加鎖操作,因此也就不會重新執行執行緒阻塞,volatile變數是一種比synchronized關鍵字輕量級的同步機制。
總的來說,當一個變數被volatile修飾後,不但具有可見性,而且還禁止指令重拍。volatile的讀效能消耗與普通變數幾乎相同,但是寫操作就慢一些,因為它要保證原生代碼中插入許多記憶體屏障指令來保證處理器不發生亂序執行。

二、對執行緒等待和喚醒的方法

2.1 常用方法介紹

在Object.java中,定義了wait(),notify()和notifyAll()等介面。wait()的作用是讓當前執行緒進入等待狀態,同時,wait()也會讓當前執行緒釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前物件上的等待執行緒;notify()是喚醒單個執行緒,而notifyAll()是喚醒所有的執行緒。

API介面 API說明
notify() 喚醒在此物件監視器上等待的單個執行緒
notifyAll() 喚醒在此物件監視器上等待的所有執行緒
wait() 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的notify()方法或是notifyAll()方法”,當前執行緒被喚醒(進入“就緒狀態”)
wait(long timeout) 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的notify()方法或notifyAll()方法,或者超過指定的時間量”,當前執行緒被喚醒(進入“就緒狀態”)
wait(long timeout,int nanos) 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法,或者其他某個執行緒中斷當前執行緒,或者已超過某個實際時間量”,當前執行緒被喚醒(進入“就緒狀態”)

2.2 wait()和notify()示例

public class WaitTest {


    /**
     * @param args
     */
    public static void main(String[] args) {

        class ThreadA extends Thread{

            public ThreadA(String name){
                super(name);
            }

            @Override
            public void run() {
                synchronized(this){
                    System.out.println(Thread.currentThread().getName()+" 正在執行");

                    System.out.println(Thread.currentThread().getName()+" 現在要執行, call notify()");
                    notify();
                }

            }
        }

          ThreadA t1 = new ThreadA("t1");

          synchronized(t1){
              //啟動執行緒t1
              System.out.println(Thread.currentThread().getName()+" start t1");
              t1.start();

              try {
                //主執行緒等待t1通過 notify()喚醒。
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

              System.out.println(Thread.currentThread().getName()+" continue");

          }
    }

}

執行結果:

main start t1
main wait()
t1 正在執行
t1 現在要執行, call notify()
main continue

結果解釋:
(1)主執行緒通過new ThreadA(“t1”);新建執行緒1,隨後通過synchronized(this)獲取“t1物件的同步鎖”。
(2)然後呼叫t1.start()啟動執行緒1;主執行緒執行t1.wait()釋放“t1物件的鎖”並且進入“等待(阻塞)狀態”,等待t1物件上的執行緒通過notify()或notifyAll()將其喚醒;
(3)執行緒t1執行之後,通過synchronized(this)獲取“當前物件的鎖”,接著通過呼叫notify()喚醒“當前物件上的等待執行緒”,也就是喚醒主執行緒;
(4)執行緒t1執行完畢之後,釋放“當前物件的鎖”,緊接著,主執行緒獲取“t1物件的鎖”,然後接著執行。

注意:t1.wait()為啥不是讓“執行緒t1”等待,而是讓“主執行緒main”等待?
JDK解釋中說,wait()的作用是讓“當前執行緒”等待,而“當前執行緒”是指正在CPU上執行的執行緒!
這也就意味著,雖然t1.wait()是通過“執行緒t1”呼叫wait()方法,但是呼叫t1.wait()的地方是在“主執行緒main”中。而主執行緒必須是“當前執行緒”,也就是執行狀態,才可以執行t1.wait()。所以“當前執行緒”是“主執行緒mian”。因此,t1.wait()是讓“主執行緒”等待,而不是“執行緒t1”。

2.3 wait(long timeout)和notify()示例

public class WaitTimeoutTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        class ThreadA extends Thread{
            public ThreadA(String name){
                super(name);
            }
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
                //死迴圈
                while(true)
                    ;
            }
        }

        ThreadA t1 = new ThreadA("t1");
        synchronized(t1){
            try {
                // 啟動“執行緒t1”
                System.out.println(Thread.currentThread().getName() + " start t1");
                t1.start();

                // 主執行緒等待t1通過notify()喚醒 或 notifyAll()喚醒,或超過3000ms延時;然後才被喚醒。
                System.out.println(Thread.currentThread().getName() + " call wait ");
                t1.wait(3000);

                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果:

main start t1
main call wait 
t1 run          //大約3秒之後,輸出“main continue”
main continue

流程解釋:
(1)主執行緒通過new ThreadA(“t1”);新建執行緒1,隨後通過synchronized(this)獲取“t1物件的同步鎖”。
(2)主執行緒main執行t1.start()啟動“執行緒t1”。
(3)主執行緒main執行t1.wait(3000),此時,主執行緒進入“阻塞狀態”。需要“用於t1物件鎖的執行緒通過notify()或者notifyAll()將其喚醒”或者“超過3000ms之後”,主執行緒main才進入“就緒狀態”,然後才可以執行。
(4)執行緒t1執行之後,進入死迴圈,會一直不斷的執行。
(5)超過3000ms之後,主執行緒會進入到“就緒狀態”,然後接著進入“執行狀態”。主執行緒main和執行緒t1會一直執行下去。

2.4 wait()和notifyAll()示例

通過前面的例項,我們知道notify()可以喚醒在此物件監視器上等待的單個執行緒。notifyAll()的作用是喚醒在此物件監視器上等待的所有執行緒。


public class NotifyAllTest {

    private static Object obj = new Object();

    static class ThreadA extends Thread{

        public ThreadA(String name){
            super(name);
        }

        @Override
        public void run() {

            synchronized(obj){

                try {
                    System.out.println(Thread.currentThread().getName()+" wait");

                    obj.wait();

                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * @param args
     */
    public static void main(String[] args) {

         ThreadA t1 = new ThreadA("t1");
         ThreadA t2 = new ThreadA("t2");
         ThreadA t3 = new ThreadA("t3");
         t1.start();
         t2.start();
         t3.start();

         try {
            System.out.println(Thread.currentThread().getName()+" sleep(3000)");
            Thread.sleep(3000);
        } catch (InterruptedException e) {

            e.printStackTrace();
        }

         synchronized(obj){
             System.out.println(Thread.currentThread().getName()+ " notifyAll()");
             obj.notifyAll();
         }
    }
}

執行結果(結果不唯一):

main sleep(3000)
t2 wait
t1 wait
t3 wait
main notifyAll()
t3 continue
t1 continue
t2 continue

流程分析:
(1)主執行緒中新建並啟動3個執行緒t1、t2和t3,並呼叫start() 方法進入“就緒狀態”,然後可能會進入“執行狀態”,所以“main sleep(3000)”不一定是第一個輸出,可能會是第二個,這要看CPU 時間片執行到那個執行緒。t1、t2和t3執行緒那個先執行時,會呼叫“obj.wait();”阻塞當前執行緒進入“阻塞狀態”,所以t1、t2和t3執行緒也會依次進入“阻塞狀態”,等待其它執行緒通過notify()或額nofityAll()來喚醒它。
(2)主執行緒通過sleep(3000)休眠3秒。在主執行緒休眠3秒的過程中,t1、t2和t3執行緒應該都運行了。
(3)主執行緒休眠3秒後,接著執行。執行obj.notifyAll();喚醒obj上等待的執行緒,即喚醒t1、t2和t3執行緒。然後,主執行緒的synchronized(obj)執行完畢,主執行緒釋放“obj物件鎖”。這樣t1、t2和t3執行緒就可以獲取“obj鎖”繼續執行。

2.5 為什麼notify()、wait()等函式定義在Object類中而不是Thread類中

  Object中的wait()、notify()等函式和synchronized一樣,會對“物件的同步鎖”進行操作。wait()會使“當前執行緒”等待,因為執行緒應該釋放它所持有的“同步鎖”,否則其他執行緒獲取不到該“同步鎖”而無法執行。執行緒呼叫wait()之後,會釋放它所持有的“同步鎖”;而且,等待執行緒可以被notify()或notifyAll()喚醒。那麼notify()是依據什麼喚醒等待執行緒的?或者wait()和notify()之間是通過什麼關聯起來的?答案是:依據“物件的同步鎖”。
  負責喚醒等待執行緒的那個執行緒(我們稱為“喚醒執行緒”),它只有在獲取“該物件的同步鎖”(這裡的同步鎖必須和等待執行緒的同步鎖是同一個),並且呼叫notify()或notifyAll()方法之後,才能喚醒等待執行緒。雖然,等待執行緒被喚醒,但是,它不能立即執行。因為喚醒執行緒還持有“該物件的同步鎖”。必須等到喚醒執行緒釋放“該物件的同步鎖”之後,等待執行緒才能獲取到“物件的同步鎖”,然後繼續執行。
  總之,notify()、wait()依賴於“同步鎖”,而“同步鎖”是該物件所持有,並且每個物件有且僅有一個。所以,你可以把wait()方法放進任何同步控制方法裡,而不用考慮這個類是繼承自Thread還是實現了Runnable介面。這就是為什麼notify()、wait()等函式定義在Object類,而不是Thread類中的原因。實際上,只能在同步控制方法或同步控制塊裡呼叫wait()、notify()和notifyAll()(因為不用操作鎖,所以sleep()可以在非同步控制方法裡呼叫)。如果在非同步控制方法裡呼叫這些方法,程式可以通過編譯,但執行時將得到IllegalMonitorStateException異常,異常的大概是當前執行緒不是擁有者。意思是,呼叫wait()、notify()和notifyAll()的任務在呼叫這些方法前必須獲取物件的鎖。

三、執行緒讓步和休眠

3.1 執行緒讓步

在Java執行緒中,yield()方法的作用是讓步,它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其他具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其他具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒有進入到“執行狀態”繼續執行。

public class YieldTest {


    public static void main(String[] args) {

        class ThreadA extends Thread{
            public ThreadA(String name){
                super(name);
            }

            @Override
            public synchronized void run() {

                for(int i=0;i<10;i++){
                    System.out.println(" "+this.getName()+" "+i);

                    if(i%2 == 0){
                        Thread.yield();
                    }
                }
            }
        }

        ThreadA t1 = new ThreadA("t1");
        ThreadA t2 = new ThreadA("t2");
        t1.start();
        t2.start();

    }

}

執行結果:

 t1 0
 t2 0
 t1 1
 t1 2
 t2 1
 t2 2
 t1 3
 t1 4
 t2 3
 t2 4
 t1 5
 t1 6
 t2 5
 t2 6
 t1 7
 t1 8
 t2 7
 t1 9
 t2 8
 t2 9

結果說明:
執行緒t1在能被2整除的時候,並不一定切換到執行緒2。這表明,yield()方法雖然可以讓執行緒由“執行狀態”進入到“就緒狀態”;但是,它不一定會讓其他執行緒獲取CPU執行權(其他執行緒進入到“執行狀態”)。即時這個“其他執行緒”與當前呼叫yield()的執行緒具有相同的優先順序。

3.2 yield()和wait()的比較

我們知道,wait()的作用是讓當前執行緒由“執行狀態”進入到“等待(阻塞)”的同時,也會釋放同步鎖。而yield()的作用是讓步,它也是讓當前執行緒離開“執行狀態”。區別是:
(1)wait()是讓執行緒由“執行狀態”進入到“等待(阻塞)狀態”,而yield()是讓執行緒由“執行狀態”進入到“就緒狀態”。
(2)wait()是會讓執行緒釋放它所持有的物件的同步鎖,而yield()方法不會釋放物件的同步鎖。

public class YieldLockTest {

    public static void main(String[] args) {

        final Object obj = new Object();

           class ThreadA extends Thread{

            public ThreadA(String name){
                super(name);
            }

            @Override
            public void run() {
                synchronized(obj){
                    for(int i=0;i<10;i++){
                        System.out.println(this.getName()+" "+i);
                        if(i%4==0){
                            Thread.yield();
                        }
                    }
                }
            }
        }

        ThreadA  t1 = new ThreadA("t1");
        ThreadA  t2 = new ThreadA("t2");
        t1.start();
        t2.start();

    }

}

執行結果:

t1 0
t1 1
t1 2
t1 3
t1 4
t1 5
t1 6
t1 7
t1 8
t1 9
t2 0
t2 1
t2 2
t2 3
t2 4
t2 5
t2 6
t2 7
t2 8
t2 9

結果說明:
執行緒t1和t2在run()會引用同一個物件的同步鎖,即synchronized(obj),在t1執行過程中,雖然它會呼叫Thread.yield();但是,t2是沒有獲取到CPU執行權的,因為,t1並沒有釋放“obj所持有的同步鎖”。

3.3 執行緒休眠

sleep()方法定義在Thread類中,sleep()的作用是讓當前執行緒休眠,即當前執行緒會從“遠端狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,執行緒休眠的時間會大於/等於該休眠時間;線上程重新被喚醒時,它會由“阻塞狀態”變成“就緒狀態”,從而等待CPU的排程執行。

3.4 sleep()和wait()的比較

我們知道,wait()的作用是讓當前的執行緒由“執行狀態”進入到“等待(阻塞)狀態”的同時,也會釋放同步鎖。但是sleep()的作用是讓當前執行緒由“執行狀態”進入到“休眠(阻塞)”狀態。wait()會釋放物件的同步鎖,而sleep()則不會釋放鎖。

public class RunnableTest {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Object obj = new Object();

        class MyThread extends Thread{

            public MyThread(String name){
                super(name);
            }
            @Override
            public void run() {
                synchronized(obj){
                    for(int i=0;i<10;i++){
                        try {
                             if (i%4 == 0)
                                 Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName()+" loop "+i);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        Thread t1 = new MyThread("t1");
        Thread t2 = new MyThread("t2");
        t1.start();
        t2.start();

    }

}

執行結果:

t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
t2 loop 5
t2 loop 6
t2 loop 7
t2 loop 8
t2 loop 9
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t1 loop 5
t1 loop 6
t1 loop 7
t1 loop 8
t1 loop 9

結果說明:
主執行緒main中啟動兩個執行緒t1和t2,t1和t2在run()方法中會引用同一個物件的同步鎖,即synchronized(obj)。在t2執行過程中,雖然它會呼叫Thread.sleep(100),但是,t1是不會獲取CPU執行權的。因為,t1並沒有釋放“obj所持有的同步鎖”。如果,註釋掉synchronized(obj)或再次執行該程式,t1和t2是可以相互切換的。

四、join()方法和interrupt()方法

4.1 加入一個執行緒

一個執行緒可以在其他執行緒之上呼叫join()方法,其效果是等待一段時間直到第二個執行緒結束結束才繼續執行。如果某個執行緒在另一個執行緒t上呼叫t.join(),此執行緒將被掛起,直到目標執行緒t結束才恢復(即t.isAlive()返回為假)。也可以在呼叫jion()時帶上一個超時引數(單位可以是毫秒,或者納秒),這樣如果目標執行緒在這段時間到期時還沒有結束的話,join()方法總能返回。對jion()方法的呼叫可以被中斷,做法是在呼叫執行緒上呼叫interrupt()方法,這時需要用到try_catch子句。

4.2 join()原始碼分析和例項

//無引數的join()方法
public final void join() throws InterruptedException {
        join(0);
}

//兩個引數的join()方法
public final synchronized void join(long millis, int nanos)throws InterruptedException {

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }

//一個引數的join()方法
public final synchronized void join(long millis)throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

原始碼說明:
主要是無引數和一個引數的join()方法,從程式碼中可以發現,當mills == 0 時,會進入while(isAlive())迴圈,即只要呼叫該join()方法的執行緒是活的,那麼當前執行緒就要不停等待。(wait()是讓當前執行緒等待)。
下面是join()例項:

public class JoinTest {


    public static void main(String[] args) {


        class ThreadA extends Thread{

            public ThreadA(String name){
                super(name);
            }

            @Override
            public void run() {
                System.out.println("start "+this.getName());

                for(int i=0;i<1000000000;i++)
                    ;

                System.out.println("finish "+ this.getName());
            }
        }

        Thread t1 = new ThreadA("t1");
        t1.start();

        try {

            t1.join();

            System.out.println("finish "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

執行結果:

start t1
finish t1
finish main

結果說明:
在主執行緒main中新建執行緒t1,接著,通過t1.start()啟動執行緒t1,並執行t1.join()。然後,主執行緒會進入“阻塞狀態”等待t1執行結束。t1結束之後,會喚醒主執行緒main,主執行緒重新獲取CPU執行權,繼續執行。

4.3 interrupt()方法

  interrupt()的作用是中斷本執行緒。本執行緒中斷自己是被允許的;其他執行緒呼叫本執行緒的interrupt()方法時,會通過checkAccess()檢查許可權。這有可能丟擲SecurityException異常。
  如果本執行緒是處於阻塞狀態:呼叫執行緒的wait(),wait(long)或wait(long,int)會讓它進入等待(阻塞)狀態,或者呼叫執行緒的join(),join(long),join(long,int),sleep(long),sleep(long,int)也會讓它進入阻塞狀態。若執行緒在阻塞狀態時,呼叫了它的interrupt()方法,那麼它的“中斷狀態”會被清理並且會收到一個InterruptedException異常。例如,執行緒通過wait()進入阻塞狀態,此時通過interrupt()中斷該執行緒,呼叫interrupt()會立即將執行緒的中斷標記設定為“true”,但是由於執行緒處於阻塞狀態,所以該“中斷標記”會立即被清除為“false”,同時,會產生一個InterruptedException異常。
  如果執行緒被阻塞在一個Selector選擇器中,那麼通過interrupt()中斷它時,執行緒的中斷標記會被設定為true,並且它會立即從選擇操作中返回。
  如果不屬於前面所說的的情況,那麼通過interrupt()中斷執行緒時,它的中斷標記會被設定為true。中斷一個“已經終止的執行緒”不會產生任何操作。

4.3 執行緒終止的方式

Thread中的stop()和suspend()方法,由於固有的不安全性,已經不建議使用。下面,通過討論執行緒在“阻塞狀態”和“執行狀態”的終止方式,然後在總結一個通用的方式。

4.3.1終止處於“阻塞狀態”的執行緒

通常,我們通過“中斷”方式終止處於“阻塞狀態”的執行緒。當執行緒由於被呼叫了sleep(),wait(),join()等方法而進入阻塞狀態;若此時呼叫執行緒的interrupt()將執行緒的中斷標記設為true。由於處於阻塞狀態,中斷標記會被清除,同時產生InterruptedException異常。將InterruptedException放在適當的為止就能終止執行緒,形如如下:

@Override
public void run() {
    try {
        while (true) {
            // 執行任務...
        }
    } catch (InterruptedException ie) {  
        // 由於產生InterruptedException異常,退出while(true)迴圈,執行緒終止!
    }
}

說明:在while(true)中不斷的執行任務,當執行緒處於阻塞狀態時,呼叫執行緒的interrupt()產生InterruptedException異常中斷。中斷的捕獲在while(true)之外,這樣就退出while(true)迴圈。
注意:對InterruptedException的捕獲一般放在while(true)迴圈體的外面,這樣產生異常時就退出了while(true)迴圈。否則,InterruptedException在while(true)迴圈體之內,就需要額外的新增退出處理。形式如下:

@Override
public void run() {
    while (true) {
        try {
            // 執行任務...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)迴圈體內。
            // 當執行緒產生了InterruptedException異常時,while(true)仍能繼續執行!需要手動退出
            break;
        }
    }
}

說明:
上面的InterruptedException異常捕獲在while(true)之內。當產生InterruptedException異常時,被catch處理之外,仍然在while(true)迴圈體內;要退出while(true)迴圈體,需要額外的執行退出while(true)的操作。

4.3.1終止處於“執行狀態”的執行緒

通常,我們通過“標記”方式終止處於“執行狀態”的執行緒。其中包括“中斷標記”和“額外新增標記”。
(1)通過“中斷標記”終止執行緒。

@Override
public void run() {
    while (!isInterrupted()) {
        // 執行任務...
    }
}

說明:
isInterrupted()來判斷執行緒中的中斷標記是不是為true。當執行緒處於執行狀態,並且我們需要終止它時,可以呼叫執行緒的interrupt()方法,使用執行緒的中斷標記為true,即isInterrupted()方法會返回true。此時,就會退出while迴圈。
注意:interrupt()並不會終止處於“執行狀態”的執行緒,它會將執行緒的中斷標記設為true。
(2)通過“額外新增標記”

private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}

@Override
public void run() {
    while (flag) {
        // 執行任務...
    }
}

說明:執行緒中有一個flag標記,它的預設值是true;並且我們提供stopTask()來設定flag標記。當我們需要終止該執行緒時,呼叫該執行緒的stopTask()方法就可以讓執行緒退出while迴圈。
注意:將flag定義為volatile型別,是為了保證flag的可見性。即其它執行緒通過stopTask()修改了flag之後,本執行緒能看到修改後的flag的值。
綜合執行緒處於“阻塞狀態”和“執行狀態”的終止方式,比較通用的終止執行緒的形式如下:

@Override
public void run() {
    try {
        // 1. isInterrupted()保證,只要中斷標記為true就終止執行緒。
        while (!isInterrupted()) {
            // 執行任務...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException異常保證,當InterruptedException異常產生時,執行緒被終止。
    }
}

4.4 終止執行緒的例項

4.4.1通過“中斷標記”終止執行緒的例項

public class InterruptBlock {

    /**
     * @param args
     */
    public static void main(String[] args) {


        class MyThread extends Thread{

            public MyThread(String name){
                super(name);
            }

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

            }
        }


        try {

            //新建
            Thread t1 = new MyThread("t1");
            System.out.println(t1.getName()+" ("+t1.getState()+" ) is new.");

            System.out.println("luo1:"+t1.isInterrupted());
            //啟動
            t1.start();
            System.out.println(t1.getName()+" ("+t1.getState()+" ) is started.");
            System.out.println("luo2:"+t1.isInterrupted());
            //主執行緒休眠300ms,然後主執行緒給t1發“中斷”指令
            Thread.sleep(300);
            t1.interrupt();
            System.out.println("luo3:"+t1.isInterrupted());
            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) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

執行結果:

t1 (NEW ) is new.
luo1:false
t1 (RUNNABLE ) is started.
luo2:false
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
luo3:true
t1 (RUNNABLE) loop 3
t1 (RUNNABLE ) is interrupted.
t1 (TERMINATED ) is interrupted now .

結果說明:
(1)主執行緒mian新建執行緒t1,之後通過t1.start()啟動執行緒t1;
(2)t1啟動之後,會不斷的檢查它的中斷標記,如果中斷標記為“false”,則休眠100ms;
(3)t1休眠之後,會切換到主執行緒main,主執行緒再次執行時,會執行t1.interrupt()中端執行緒t1。t1收到中斷指令之後,會將t1的中斷標誌設定為“false”,而且會丟擲IterruptedException異常。在t1的run()方法中,是在迴圈體while(true)之外獲取的異常,因此迴圈被終止。
如果,將run()方法中捕獲InterruptedException異常的程式碼塊移到while迴圈體內,那麼程式會變成一個死迴圈。解決的辦法是,可以在catch中新增break或return就可以解決該問題。
下面通過“額外新增標記”的方式終止“阻塞狀態的”

4.4.2通過“額外新增標記”終止執行緒的例項

public class InterruptBlock {

    /**
     * @param args
     */
    public static void main(String[] args) {



        class MyThread extends Thread{

            private volatile boolean flag = true;

            public void stopTask(){
                flag = false;
            }

            public MyThread(String name){
                super(name);
            }

            @Override
            public void run() { 
                synchronized(this){

                    try {
                        int i=0;
                        while(flag){
                             Thread.sleep(100);
                             i++;
                             System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") loop "+i);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") catch InterruptedExecption");
                    }   
                }
            }
        }


        try {

            //新建
            MyThread t1 = new MyThread("t1");
            System.out.println(t1.getName()+" ("+t1.getState()+" ) is new.");

            //System.out.println("luo1:"+t1.isInterrupted());
            //啟動
            t1.start();
            System.out.println(t1.getName()+" ("+t1.getState()+" ) is started.");
            //System.out.println("luo2:"+t1.isInterrupted());
            //主執行緒休眠300ms,然後主執行緒給t1發“中斷”指令
            Thread.sleep(300);
            //t1.interrupt();
            t1.stopTask();
            //System.out.println("luo3:"+t1.isInterrupted());
            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) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

執行結果:

t1 (NEW ) is new.
t1 (RUNNABLE ) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING ) is interrupted.
t1 (RUNNABLE) loop 3
t1 (TERMINATED ) is interrupted now .

4.5 interrupt()和isInterrupted()的區別

interrupt()和isInterrupted()都能夠用於檢測物件的“中斷標記”。區別是:interrupt()除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設為false);而isInterrupted()僅僅返回中斷標記。

五、執行緒優先順序和守護執行緒

  Java中執行緒的優先順序的範圍是1~10,預設的優先順序為5。“高優先順序執行緒”會優先於“低優先順序執行緒”執行。而且Java中有兩種執行緒:使用者執行緒和守護執行緒。可以通過isDeamon()方法來區別它們:如果返回false,則說明該執行緒是“使用者執行緒”;否則就是“守護執行緒”。使用者執行緒一般使用者執行使用者級任務,而守護執行緒也就是“後臺執行緒”,一般用來執行後臺任務。需要注意的是:JVM在“使用者執行緒”都結束後會退出。
  每個執行緒都有一個優先順序。“高優先順序執行緒”會優先於“低優先順序執行緒”執行。每個執行緒都可以被標記為一個守護程序或非守護程序。在一些執行的主執行緒中建立新的子執行緒時,子執行緒的優先順序被設定為等於“建立它的主執行緒的優先順序”,當且僅當“建立它的主執行緒是守護執行緒”時“子執行緒才會是守護執行緒”。

當Java虛擬機器啟動時,通常有一個單一的非守護執行緒(該執行緒通過是通過main()方法啟動)。JVM會一直執行直到下面的任意一個條件發生,JVM就會終止執行:
(1) 呼叫了exit()方法