java線程總結(jié)

java線程:概念與原理


一、操作系統(tǒng)中線程和進(jìn)程的概念

現(xiàn)在的操作系統(tǒng)是多任務(wù)的操作系統(tǒng),多線程是多任務(wù)的一種實(shí)現(xiàn)方式。

進(jìn)程是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序,每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程可以啟動(dòng)多個(gè)線程。比如,在Windows系統(tǒng)中,一個(gè)運(yùn)行的exe就是一個(gè)進(jìn)程。

線程是指進(jìn)程中的一個(gè)執(zhí)行流程,一個(gè)進(jìn)程可以運(yùn)行多個(gè)線程。比如,java.exe進(jìn)程中可以運(yùn)行多個(gè)線程,線程總屬于某個(gè)進(jìn)程,進(jìn)程中的多個(gè)線程共享進(jìn)程的內(nèi)存。

二、Java中的線程

在Java中,“線程”指兩個(gè)不同的事情:

    1. java.lang.Thread類的一個(gè)實(shí)例
    1. 線程的執(zhí)行

使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來(lái)定義,實(shí)例化和啟動(dòng)線程。

一個(gè)Thread類實(shí)力只是一個(gè)對(duì)象,像Java中的任何其他對(duì)象一樣,具有變量和方法,生死于堆上。

Java中每個(gè)線程都有一個(gè)調(diào)用棧,即使不在程序中創(chuàng)建任何新的線程,線程也在后臺(tái)運(yùn)行著。

一個(gè)Java應(yīng)用總是從main()方法開始運(yùn)行,main()方法運(yùn)行在一個(gè)線程里,稱為主線程。

一旦創(chuàng)建一個(gè)新線程,就產(chǎn)生一個(gè)新的調(diào)用棧。

線程總體分為兩類:用戶線程和守護(hù)線程。
當(dāng)所有用戶線程執(zhí)行完畢,JVM自動(dòng)關(guān)閉。但守護(hù)線程卻不獨(dú)立于JVM,守護(hù)線程一般由操作系統(tǒng)或者用戶自己創(chuàng)建。


Java線程:創(chuàng)建與啟動(dòng)

一、定義線程

1.擴(kuò)展java.lang.Thread類

此類中有個(gè)run()方法,應(yīng)該注意其用法:
public void run()
如果該線程是使用獨(dú)立的Runnable運(yùn)行對(duì)象構(gòu)造的,則調(diào)用該Runnable對(duì)象的run方法;否則該方法不執(zhí)行任何操作并返回。

Thread的子類應(yīng)該重寫該方法。

2.實(shí)現(xiàn)java.lang.Runnable接口

void run()
使用實(shí)現(xiàn)Runnable的對(duì)象創(chuàng)建一個(gè)線程時(shí),啟動(dòng)該線程將導(dǎo)致在獨(dú)立執(zhí)行的線程中調(diào)用對(duì)象的run()方法。

二、實(shí)例化線程

1.擴(kuò)展java.lang.Runnable類的線程,直接new即可

例:

    public class MyThread extends Thread {
    private int i = 0;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (i = 0; i < 100; i++) {
            System.out.println(MyThread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread myThread1 = new MyThread(); // 創(chuàng)建一個(gè)新的線程 myThread1 此線程進(jìn)入新建狀態(tài)
                Thread myThread2 = new MyThread(); // 創(chuàng)建一個(gè)新的線程 myThread2 此線程進(jìn)入新建狀態(tài)
                myThread1.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
                myThread2.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
            }
        }
    }
}

2. 如果是實(shí)現(xiàn)了java.lang.Runnable接口的類,則用Thread的構(gòu)造方法

Thread(Runnable target)

例:

    public class MyRunnable implements  Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("MyRunnable run");
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myrunable = new MyRunnable();
                Thread mythread = new MyThread(myrunable);
                mythread.start();
            }
        }
    }

}

三、啟動(dòng)線程

在線程的thread對(duì)象上調(diào)用start()方法,而不是run()方法或其他方法。

在調(diào)用start()方法后:
啟動(dòng)新的線程(具有新的調(diào)用棧)
該線程從新建狀態(tài)轉(zhuǎn)移到就緒狀態(tài),等待CPU執(zhí)行
當(dāng)該線程執(zhí)行時(shí),其run()方法將運(yùn)行

注意:對(duì)Java來(lái)說(shuō),run()方法沒(méi)有任何特別之處。像main()方法一樣,它只是新線程知道調(diào)用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調(diào)用run方法是合法的。但并不啟動(dòng)新的線程。

五、常見(jiàn)的問(wèn)題

1、線程的名字,一個(gè)運(yùn)行中的線程總是有名字的,名字有兩個(gè)來(lái)源,一個(gè)是虛擬機(jī)給自己的名字,一個(gè)是自己定的名字。在沒(méi)有指定線程名字的情況下,虛擬機(jī)總會(huì)為線程指定名字,且主線程的名字總是main,非主線程的名字不確定。

2、線程都可以設(shè)定名字,也可以獲取線程的名字。

3、獲取當(dāng)前線程的對(duì)象的方法:Thread.currentThread()。

4、對(duì)于任何一組啟動(dòng)的線程來(lái)說(shuō),調(diào)度程序不能保證其執(zhí)行順序,持續(xù)時(shí)間也無(wú)法保證。

5、當(dāng)線程的目標(biāo)run()方法結(jié)束時(shí),該線程完成。

6、一旦線程啟動(dòng),永遠(yuǎn)不能重啟,只有一個(gè)新線程可以被啟動(dòng),且只能一次。一個(gè)可運(yùn)行的線程或死線程可以被重新啟動(dòng)。

7、線程的調(diào)度是JVM的一部分,在一個(gè)CPU的機(jī)器上上,實(shí)際上一次只能運(yùn)行一個(gè)線程。一次只有一個(gè)線程棧執(zhí)行。JVM線程調(diào)度程序決定實(shí)際運(yùn)行哪個(gè)處于可運(yùn)行狀態(tài)的線程。
眾多可運(yùn)行線程中的某一個(gè)會(huì)被選中做為當(dāng)前線程。可運(yùn)行線程被選擇運(yùn)行的順序是沒(méi)有保障的。

Java線程:線程棧模型與線程的變量

要理解縣城的調(diào)度原理,以及線程的執(zhí)行過(guò)程,必須理解線程棧模型。

線程棧:是指某時(shí)刻時(shí),內(nèi)存中線程調(diào)度的棧信息,當(dāng)前調(diào)用的方法總是位于棧頂。線程棧的內(nèi)容是隨著程序運(yùn)行動(dòng)態(tài)變化的,因此研究線程棧必須選擇某一運(yùn)行時(shí)刻(實(shí)際上指代碼運(yùn)行到什么地方)

例:線程(調(diào)用)棧的變化過(guò)程。


200809131221317983968.png

這幅圖描述在代碼執(zhí)行到兩個(gè)不同時(shí)刻1、2時(shí)候,虛擬機(jī)線程調(diào)用棧示意圖.

當(dāng)程序執(zhí)行到t.start();時(shí)候,程序多出一個(gè)分支(增加了一個(gè)調(diào)用棧B),這樣,棧A、棧B并行執(zhí)行.

從這里就可以看出方法調(diào)用和線程啟動(dòng)的區(qū)別了。


Java線程:線程狀態(tài)的轉(zhuǎn)換

一、線程的狀態(tài)

線程的狀態(tài)轉(zhuǎn)換是線程的基礎(chǔ),現(xiàn)成的狀態(tài)總體可分為五大狀態(tài):


xiancheng.jpg
  • 新建狀態(tài)(New):當(dāng)線程對(duì)象對(duì)創(chuàng)建后,即進(jìn)入了新建狀態(tài),
    如:Thread t = new MyThread();
  • 就緒狀態(tài)(Runnable):當(dāng)調(diào)用線程對(duì)象的start()方法(t.start();),線程即進(jìn)入就緒狀態(tài)。
    處于就緒狀態(tài)的線程,只是說(shuō)明此線程已經(jīng)做好了準(zhǔn)備,隨時(shí)等待CPU調(diào)度執(zhí)行,并不是說(shuō)執(zhí)行了t.start()此線程立即就會(huì)執(zhí)行
  • 執(zhí)行狀態(tài)(Running):當(dāng)CPU開始調(diào)度處于就緒狀態(tài)的線程時(shí),此時(shí)線程才得以真正執(zhí)行,即進(jìn)入到運(yùn)行狀態(tài)。

注:就緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,也就是說(shuō),線程要想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中。

  • 阻塞狀態(tài)(Blocked):處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對(duì)CPU的使用權(quán),停止執(zhí)行,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才有機(jī)會(huì)再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。
  • 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

二、阻塞的三種情況

1.等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進(jìn)入到等待阻塞狀態(tài);

2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用),它會(huì)進(jìn)入同步阻塞狀態(tài);

3.其他阻塞 -- 通過(guò)調(diào)用線程的sleep()或join()或發(fā)出了I/O請(qǐng)求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。

睡眠

Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態(tài)方法強(qiáng)制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)以“減慢線程”。

線程睡眠的原因:線程執(zhí)行太快,或者需要強(qiáng)制進(jìn)入下一輪,因?yàn)镴ava規(guī)范不保證合理的輪換。

睡眠的實(shí)現(xiàn):
調(diào)用靜態(tài)方法sleep()

    try{
        Thread.sleep(1);
    }catch((InterruptedException e) {
        e.printStackTrace(); 
    }

為了讓其他線程有機(jī)會(huì)執(zhí)行,可以將Thread.sleep()的調(diào)用放在線程run()之內(nèi),這樣才能保證線程在執(zhí)行過(guò)程中會(huì)睡眠。

注意:

  • 線程睡眠是幫助所有線程獲得運(yùn)行機(jī)會(huì)的最好方法。
  • 線程睡眠到期自動(dòng)蘇醒,并返回就緒狀態(tài),不是運(yùn)行狀態(tài)。sleep()方法并不能保證該線程睡眠到期后立即執(zhí)行。
  • sleep()是靜態(tài)方法,只能控制當(dāng)前正在運(yùn)行的線程。

線程的優(yōu)先級(jí)和線程讓步y(tǒng)ield()

線程的讓步是通過(guò)Thread.yield()方法來(lái)實(shí)現(xiàn)的。yield方法的作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。

要理解yield(),必須了解線程的優(yōu)先級(jí)的概念。
線程總是存在優(yōu)先級(jí),優(yōu)先級(jí)范圍在1~10之間。JVM線程調(diào)度程序是基于優(yōu)先級(jí)的搶先調(diào)度機(jī)制。在大多數(shù)情況下,當(dāng)前運(yùn)行的線程的優(yōu)先級(jí)大于或等于線程池中任何線程的優(yōu)先級(jí)。

注意:當(dāng)設(shè)計(jì)多線程應(yīng)用程序,一定不要依賴于線程的優(yōu)先級(jí),因?yàn)榫€程的優(yōu)先級(jí)操作是沒(méi)有保證的,只能把線程的優(yōu)先級(jí)作為提高程序效率的方法,但不要依賴這種操作。

設(shè)置線程的優(yōu)先級(jí):線程默認(rèn)的優(yōu)先級(jí)是創(chuàng)建他的執(zhí)行線程的優(yōu)先級(jí),可通過(guò)setPriority(int newPriority)更改線程的優(yōu)先級(jí)。

    Thread t = new MyThread();
    t.setPriority(8);
    t.start();

yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到就緒狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)。
因此,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。

join()方法

Thread的非靜態(tài)方法join()讓一個(gè)線程B“加入”到另一個(gè)線程A的尾部。在A執(zhí)行完畢前,B不能工作。例如:

    Thread t = new MyThread();
    t.start();
    t.join();

另外,join()方法還有帶超時(shí)限制的重載版本。例如t.join(5000);則讓線程等待5000毫秒,如果超過(guò)這個(gè)時(shí)間,則停止等待,變?yōu)榭蛇\(yùn)行狀態(tài)。

小結(jié)

到目前為止,線程離開運(yùn)行狀態(tài)的3種方法:
1、調(diào)用Thread.sleep():使線程睡眠至少多少毫秒;

2、調(diào)用Thread.yield():不能保障太多事情,盡管通常它會(huì)讓當(dāng)前運(yùn)行線程回到可運(yùn)行性狀態(tài),使得有相同優(yōu)先級(jí)的線程有機(jī)會(huì)執(zhí)行。

3、調(diào)用join()方法:保證當(dāng)前線程停止執(zhí)行,直到該線程所加入的線程完成為止。然而,如果它加入的線程沒(méi)有存活,則當(dāng)前線程不需要停止。

除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運(yùn)行狀態(tài):

1、線程的run()方法完成。

2、在對(duì)象上調(diào)用wait()方法(不是在線程上調(diào)用)。

3、線程不能在對(duì)象上獲得鎖定,它正試圖運(yùn)行該對(duì)象的方法代碼。

4、線程調(diào)度程序可以決定將當(dāng)前運(yùn)行狀態(tài)移動(dòng)到可運(yùn)行狀態(tài),以便讓另一個(gè)線程獲得運(yùn)行機(jī)會(huì),而不需要任何理由。

內(nèi)容轉(zhuǎn)載自:http://blog.csdn.net/shimiso

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    勝浩_ae28閱讀 5,257評(píng)論 0 23
  • 單任務(wù) 單任務(wù)的特點(diǎn)是排隊(duì)執(zhí)行,也就是同步,就像再cmd輸入一條命令后,必須等待這條命令執(zhí)行完才可以執(zhí)行下一條命令...
    Steven1997閱讀 1,356評(píng)論 0 6
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    小徐andorid閱讀 2,993評(píng)論 3 53
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來(lái)依藍(lán)閱讀 7,470評(píng)論 3 87
  • 今天周五,嫂子邀請(qǐng)我們?nèi)乙黄鹑ニ页燥?。聚?huì)時(shí)間也是盡量依著孩子周末的閑暇時(shí)間確定的。我們很高興,這是我們...
    小獅子的狼媽媽閱讀 242評(píng)論 0 2

友情鏈接更多精彩內(nèi)容