1. 程式人生 > >Java多線程 5:Thread中的實例方法

Java多線程 5:Thread中的實例方法

守護 nds 屬性 exc pre 在線 結束 正在 ini

Thread類中的方法調用方式:快速到底

學習 Thread 類中的方法是學習多線程的第一步。在學習多線程之前特別提出一點,調用 Thread 中的方法的時候,在線程類中(千萬別忘記了這個前提條件),有兩種方式,一定要理解這兩種方式的區別:

1、this.XXX()

這種調用方式表示的線程是線程實例本身

2、Thread.currentThread.XXX() 或 Thread.XXX()

上面兩種寫法是一樣的意思。這種調用方式表示的線程是正在執行 Thread.currentThread.XXX() 所在代碼塊的線程

當然,這麽說,肯定有人不理解兩者之間的差別。沒有關系,之後會講清楚,尤其是在講 Thread 構造函數這塊。講解後,再回過頭來看上面 2 點,會加深理解。

Thread類中的實例方法

從 Thread 類中的實例方法和類方法的角度講解 Thread 中的方法,這種區分的角度也有助於理解多線程中的方法。實例方法,只和實例線程(也就是 new 出來的線程)本身掛鉤,和當前運行的是哪個線程無關。看下 Thread 類中的實例方法:

1、start()

start() 方法的作用講得直白點就是通知"線程規劃器",此線程可以運行了,正在等待 CPU 調用線程對象執行 run() 方法,產生一個異步執行的效果。有一點註意:調用 start() 方法的順序不代表線程啟動的順序,線程啟動順序具有不確定性

2、run()

run() 方法是由線程調用 start() 方法之後,線程開始運行了,執行的方法。


3、isAlive()

isAlive() 方法的作用是測試線程是否處於活動狀態

看一個 Demo

public class MyThread extends Thread
{
    public void run()
    {
        System.out.println("run = " + this.isAlive());  // this 就表示的就是 mt 線程(註意和 Thread 的區別)
    }
}
public static void main(String[] args) throws Exception
{
    MyThread mt 
= new MyThread(); System.out.println("begin == " + mt.isAlive()); // 這裏還沒有啟動,所以是 false mt.start();
Thread.sleep(
100);   System.out.println("end == " + mt.isAlive()); // main 線程睡了 100ms,mt 線程肯定執行完了,所以返回 false }

運行結果

begin == false
run = true
end == false

根據結果我們可以得出結論:只要線程啟動且沒有終止,方法返回的就是 true

4、getId()

這個方法比較簡單,就不寫例子了。在一個 Java 應用中,有一個long型的全局唯一的線程 ID 生成器 threadSeqNumber,每 new 出來一個線程都會把這個自增一次,並賦予線程的tid屬性,這個是 Thread 自己做的,用戶無法執行一個線程的 Id。

5、getName()

這個方法也比較簡單,也不寫例子了。我們 new 一個線程的時候,可以指定該線程的名字,也可以不指定。如果指定,那麽線程的名字就是我們自己指定的,getName() 返回的也是開發者指定的線程的名字;如果不指定,那麽 Thread 中有一個int型全局唯一的線程初始號生成器 threadInitNum,Java先把 threadInitNum 自增,然後以 "Thread-threadInitNum" 的方式來命名新生成的線程

6、getPriority() 和 setPriority(int newPriority)

這兩個方法用於獲取和設置線程的優先級,優先級高的 CPU 得到的 CPU 資源比較多,設置優先級有助於幫"線程規劃器"確定下一次選擇哪一個線程優先執行。換句話說,兩個在等待 CPU 的線程,優先級高的線程越容易被 CPU 選擇執行。下面來看一下例子,並得出幾個結論:

public class MyThread_01 extends Thread
{
    public void run()
    {
        System.out.println("MyThread_01 priority = " + this.getPriority());
    }
}
public class MyThread_02 extends Thread
{
    public void run()
    {
        System.out.println("MyThread_02 priority = " + this.getPriority());
        MyThread_01 myThread_01 = new MyThread_01();
        myThread_01.start();
    }
}
public static void main(String[] args)
{
    System.out.println("main thread, priority = " + Thread.currentThread().getPriority());
    MyThread_02 myThread_02 = new MyThread_02();
    myThread_02.start();
}

運行結果

main thread, priority = 5
MyThread_02 priority = 5
MyThread_01 priority = 5

從這個例子我們得出結論:線程默認優先級為5,如果不手動指定,那麽線程優先級具有繼承性,比如線程A啟動線程B,那麽線程B的優先級和線程A的優先級相同

下面的 Demo 演示了設置線程優先級帶來的效果:

package com.tkz;

public class MyThread_01 extends Thread
{
    public void run()
    {
        long beginTime = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++){}
        long endTime = System.currentTimeMillis();
        System.out.println("◆◆◆◆◆ thread0 use time = " + (endTime - beginTime));
    }
    
    public static void main(String[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            MyThread_01 mt0 = new MyThread_01();
            mt0.setPriority(10);  // 在這裏極端一點更能看出效果
            mt0.start();
            MyThread_02 mt1 = new MyThread_02();
            mt1.setPriority(1);
            mt1.start();
        }
    }
}

class MyThread_02 extends Thread
{
    public void run()
    {
        long beginTime = System.currentTimeMillis();
        for (int j = 0; j < 100000; j++){}
        long endTime = System.currentTimeMillis();
        System.out.println("◇◇◇◇◇ thread1 use time = " + (endTime - beginTime));
    }
}

運行結果

◆◆◆◆◆ thread0 use time = 2
◇◇◇◇◇ thread1 use time = 1
◆◆◆◆◆ thread0 use time = 2
◆◆◆◆◆ thread0 use time = 0
◆◆◆◆◆ thread0 use time = 4
◆◆◆◆◆ thread0 use time = 4
◇◇◇◇◇ thread1 use time = 0
◇◇◇◇◇ thread1 use time = 2
◇◇◇◇◇ thread1 use time = 0
◇◇◇◇◇ thread1 use time = 10

從這個運行結果來看基本能得出結論:優先級越高的線程越能獲取 CPU 資源

7、isDaeMon、setDaemon(boolean on)

講解兩個方法前,首先要知道理解一個概念。Java 中有兩種線程,一種是用戶線程,一種是守護線程。守護線程是一種特殊的線程,它的作用是為其他線程的運行提供便利的服務,最典型的應用便是 GC 線程。如果進程中不存在非守護線程了,那麽守護線程自動銷毀,因為沒有存在的必要,為別人服務,結果服務的對象都沒了,當然就銷毀了。理解了這個概念後,看一下例子

public class MyThread extends Thread
{
    private int i = 0;
    
    public void run()
    {
        try
        {
            while (true)
            {
                i++;
                System.out.println("i = " + i);
                Thread.sleep(1000);
            }
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args)
    {
        try
        {
            MyThread mt = new MyThread();
            mt.setDaemon(true);
            mt.start();
            
            Thread.sleep(5000); // main 線程在這裏睡了 5 秒,那麽 mt 作為守護線程在這 5s 內做了哪些事
            
            System.out.println("我離開thread對象再也不打印了,我停止了!");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

運行結果

i = 1
i = 2
i = 3
i = 4
i = 5
main 線程即將運行結束了

8、interrupt()

這是一個有點誤導性的名字,實際上 Thread 類的 interrupt() 方法無法中斷線程。看一下例子:

public class TestThreadInterupt
{
    public static void main(String[] args)
    {
        try
        {
            MyThread mt = new MyThread();
            mt.start();
            Thread.sleep(1000); // main 線程睡了 2s
            
            mt.interrupt();
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

class MyThread extends Thread
{
    @Override
    public void run()
    {
        for (int i = 0; i < 500000; i++)
        {
            System.out.println("i = " + (i + 1));
        }
    }
}

運行結果

...
i = 499995
i = 499996
i = 499997
i = 499998
i = 499999
i = 500000

看結果還是打印到了 50000。也就是說,盡管調用了 interrupt() 方法,但是線程並沒有停止。interrupt() 方法的作用實際上是:在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞狀態。換句話說,沒有被阻塞的線程,調用interrupt() 方法是不起作用的。關於這個會在之後講中斷機制的時候,專門寫一篇文章講解。

9、isInterrupted()

測試線程是否已經中斷,但不清除狀態標識。這個和 interrupt() 方法一樣,在後面講中斷機制的文章中專門會講到。

10、join()

講解 join()方法之前要對 wait()/notify()/notifyAll() 機制已熟練掌握。

join( )方法的作用是等待線程銷毀。join() 方法反應的是一個很現實的問題,比如 main 線程的執行時間是1s,子線程的執行時間是10s,但是主線程依賴子線程執行完的結果,這時怎麽辦?可以像生產者/消費者模型一樣,搞一個緩沖區,子線程執行完把數據放在緩沖區中,通知 main 線程,main 線程去拿,這樣就不會浪費 main 線程的時間了。另外一種方法,就是 join() 了。

看一個 Demo

public class TestThreadInterupt
{
    public static void main(String[] args) throws Exception
    {
        MyThread mt = new MyThread();
            mt.start();
            mt.join();
            
            System.out.println("我是 "+Thread.currentThread().getName()+" 線程,當 mt 線程執行完畢之後我再執行");
    }
}

class MyThread extends Thread
{
    public void run()
    {
        try
        {
            int secondValue = (int)(Math.random() * 10000);
            System.out.println(secondValue);
            
            Thread.sleep(secondValue);
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

運行結果

8241
我是 main 線程,當 mt 線程執行完畢之後我再執行

join() 方法會使調用 join() 方法的線程(也就是 mt 線程)所在的線程(也就是 main 線程)無限阻塞,直到調用 join() 方法的線程銷毀為止,此例中 main 線程就會無限期阻塞直到 mt 的 run() 方法執行完畢。

join() 方法的一個重點是要區分出和 sleep() 方法的區別。join(2000) 也是可以的,表示調用 join() 方法所在的線程最多等待 2000ms,兩者的區別在於:

sleep(2000) 不釋放鎖,join(2000) 釋放鎖,因為 join() 方法內部使用的是 wait(),因此會釋放鎖。看一下 join(2000) 的源碼就知道了,join() 其實和 join(2000) 一樣,無非是 join(0) 而已:

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; } } }

Java多線程 5:Thread中的實例方法