1. 程式人生 > >java--淺談線程(二、線程的方法和狀態)

java--淺談線程(二、線程的方法和狀態)

println block not 問題: inter 方法 pre 源碼 單個

1.線程的狀態介紹:

技術分享圖片

說明
線程共包括以下5種狀態。
1. 新建狀態(New) : 線程對象被創建後,就進入了新建狀態。例如,Thread thread = new Thread()。
2. 就緒狀態(Runnable): 也被稱為“可執行狀態”。線程對象被創建後,其它線程調用了該對象的start()方法,從而來啟動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
3. 運行狀態(Running) : 線程獲取CPU權限進行執行。需要註意的是,線程只能從就緒狀態進入到運行狀態。
4. 阻塞狀態(Blocked) : 阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:

(01) 等待阻塞 -- 通過調用線程的wait()方法,讓線程等待某工作的完成。
(02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態。
(03) 其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead) : 線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

2.線程基本方法介紹:

1. wait(), notify(), notifyAll()等方法介紹

在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。

Object類中關於等待/喚醒的API詳細信息如下:
notify() -- 喚醒在此對象監視器上等待的單個線程。
notifyAll() -- 喚醒在此對象監視器上等待的所有線程。
wait() -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。

wait(long timeout) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。

2. 為什麽notify(), wait()等函數定義在Object中,而不是Thread中

Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。

wait()會使“當前線程”等待,因為線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!
OK,線程調用wait()之後,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒。現在,請思考一個問題:notify()是依據什麽喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什麽關聯起來的?答案是:依據“對象的同步鎖”。

負責喚醒等待線程的那個線程(我們稱為“喚醒線程”),它只有在獲取“該對象的同步鎖”(這裏的同步鎖必須和等待線程的同步鎖是同一個),並且調用notify()或notifyAll()方法之後,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因為喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之後,等待線程才能獲取到“對象的同步鎖”進而繼續運行。

總之,notify(), wait()依賴於“同步鎖”,而“同步鎖”是對象鎖持有,並且每個對象有且僅有一個!這就是為什麽notify(), wait()等函數定義在Object類,而不是Thread類中的原因。

(所以說喚醒就是開鎖,而鎖又在對象中,所以定義在了Object中)

3. yield()介紹

yield()的作用是讓步。它能讓當前線程由“運行狀態”進入到“就緒狀態”,從而讓其它具有相同優先級的等待線程獲取執行權;但是,並不能保證在當前線程調用yield()之後,其它具有相同優先級的線程就一定能獲得執行權;也有可能是當前線程又進入到“運行狀態”繼續運行!

4. yield() 與 wait()的比較

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

// YieldLockTest.java 的源碼
public class YieldLockTest{ 

    private static Object obj = new Object();

    public static void main(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 

    static class ThreadA extends Thread{
        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            // 獲取obj對象的同步鎖
            synchronized (obj) {
                for(int i=0; i <10; i++){ 
                    System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 
                    // i整除4時,調用yield
                    if (i%4 == 0)
                        Thread.yield();
                }
            }
        } 
    } 
}
t1 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t1 [5]:9
t2 [5]:0
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t2 [5]:9

結果說明
主線程main中啟動了兩個線程t1和t2。t1和t2在run()會引用同一個對象的同步鎖,即synchronized(obj)。在t1運行過程中,雖然它會調用Thread.yield();但是,t2是不會獲取cpu執行權的。因為,t1並沒有釋放“obj所持有的同步鎖”!

5. sleep()介紹

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

6. sleep() 與 wait()的比較

我們知道,wait()的作用是讓當前線程由“運行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而sleep()的作用是也是讓當前線程由“運行狀態”進入到“休眠(阻塞)狀態”。
但是,wait()會釋放對象的同步鎖,而sleep()則不會釋放鎖。
下面通過示例演示sleep()是不會釋放鎖的。

// SleepLockTest.java的源碼
public class SleepLockTest{ 

    private static Object obj = new Object();

    public static void main(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 

    static class ThreadA extends Thread{
        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            // 獲取obj對象的同步鎖
            synchronized (obj) {
                try {
                    for(int i=0; i <10; i++){ 
                        System.out.printf("%s: %d\n", this.getName(), i); 
                        // i能被4整除時,休眠100毫秒
                        if (i%4 == 0)
                            Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } 
    } 
}

說明:主線程main中啟動了兩個線程t1和t2。t1和t2在run()會引用同一個對象的同步鎖,即synchronized(obj)。在t1運行過程中,雖然它會調用Thread.sleep(100);但是,t2是不會獲取cpu執行權的。因為,t1並沒有釋放“obj所持有的同步鎖”!
註意,若我們註釋掉synchronized (obj)後再次執行該程序,t1和t2是可以相互切換的。

7. interrupt()方法介紹:

package com.youyou.ch1.safeend;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 *類說明:如何安全的中斷線程
 */
public class EndThread {

    private static SimpleDateFormat formater
            = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");

    private static class UseThread extends Thread{

        public UseThread(String threadName){
            super(threadName);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (!isInterrupted()){
                System.out.println("UseThread:"+formater.format(new Date()));
                System.out.println(threadName+" is run!");
            }
            System.out.println(threadName+" interrput flag is "+isInterrupted());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("endThread");
        endThread.start();
        System.out.println("Main:"+formater.format(new Date()));
        Thread.sleep(500);
        System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
        endThread.interrupt();

    }
}

package com.youyou.ch1.safeend;

public class EndRunnable {

public static class UseRunnable implements Runnable{

@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()){
System.out.println(threadName + " is run!");
}
System.out.println(threadName + " isInterrupted flag is = " +
Thread.currentThread().isInterrupted());
}
}

public static void main(String args[]) throws InterruptedException {
UseRunnable useRunnable = new UseRunnable();
Thread thread = new Thread(useRunnable,"endThread");
thread.start();
Thread.sleep(20);
thread.interrupt();
}
}
 
package com.youyou.ch1.safeend;

import java.text.SimpleDateFormat;
import java.util.Date;

public class HasInterrputException {
    private static SimpleDateFormat formater
            = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");

    private static class UseThread extends Thread{
        public UseThread(String name){
            super(name);
        }

        @Override
        public void run(){
            String threadName = Thread.currentThread().getName();
            while (!isInterrupted()){
                try {
                    System.out.println("UseThread:"+formater.format(new Date()));
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    //當跑出異常的時候,標誌位還會被致為false,程序還是會執行,要是想中斷,
                    //還需要在這個地方再次調用.interrupt() 方法;
                    System.out.println(threadName+" catch interrput flag is "
                            +isInterrupted()+ " at "
                            +(formater.format(new Date())));
                    e.printStackTrace();
                }
            }
            System.out.println(threadName+" interrput flag is "
                    +isInterrupted());
        }
    }

    public static void main(String args[]) throws InterruptedException {
        Thread endThread = new UseThread("yyThread");
        endThread.start();
        System.out.println("Main:"+formater.format(new Date()));
        Thread.sleep(800);
        System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
        endThread.interrupt();
    }
}

7. start() 和 run() 區別介紹:

package com.youyou;

/**
 * Thread的run()方法和start方法區別;
 * run() -- 作為了普通方法
 * start() -- 才是線程開始情況
 */
public class ThreadStartAndRun {
    public static class ThreadRun extends Thread{

        @Override
        public void run(){
            int i = 90;
            while (i > 0){
                try {
                    Thread.sleep(1000);
                    System.out.println("i am " + Thread.currentThread().getName()
                    + " and now the i = " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadRun run = new ThreadRun();
        run.setName("youyou");
        //run.run();
        run.start();
    }
}

java--淺談線程(二、線程的方法和狀態)