Java線程基礎(chǔ)知識(shí)

線程的狀態(tài)

  • 新建狀態(tài):用new語(yǔ)句創(chuàng)建的線程對(duì)象處于新建狀態(tài),此時(shí)它和其它的java對(duì)象一樣,僅僅在堆中被分配了內(nèi)存
  • 就緒狀態(tài):當(dāng)一個(gè)線程創(chuàng)建了以后,其他的線程調(diào)用了它的start()方法,該線程就進(jìn)入了就緒狀態(tài)。處于這個(gè)狀態(tài)的線程位于可運(yùn)行池中,等待獲得CPU的使用權(quán)
  • 運(yùn)行狀態(tài):處于這個(gè)狀態(tài)的線程占用CPU,執(zhí)行程序的代碼
  • 阻塞狀態(tài):當(dāng)線程處于阻塞狀態(tài)時(shí),java虛擬機(jī)不會(huì)給線程分配CPU,直到線程重新進(jìn)入就緒狀態(tài),它才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。 可以細(xì)分為三種情況:
    • 位于對(duì)象等待池中的阻塞狀態(tài):當(dāng)線程運(yùn)行時(shí),如果執(zhí)行了某個(gè)對(duì)象的wait()方法,java虛擬機(jī)就回把線程放到這個(gè)對(duì)象的等待池中
    • 位于對(duì)象鎖中的阻塞狀態(tài),當(dāng)線程處于運(yùn)行狀態(tài)時(shí),試圖獲得某個(gè)對(duì)象的同步鎖時(shí),如果該對(duì)象的同步鎖已經(jīng)被其他的線程占用,JVM就會(huì)把這個(gè)線程放到這個(gè)對(duì)象的瑣池中。
    • 其它的阻塞狀態(tài):當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其它線程的join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),就會(huì)進(jìn)入這個(gè)狀態(tài)中。
shunxutu.png

線程的優(yōu)先級(jí)

  • 當(dāng)線程的優(yōu)先級(jí)沒(méi)有指定時(shí),所有線程都攜帶普通優(yōu)先級(jí)。
  • 優(yōu)先級(jí)可以用從1到10的范圍指定。10表示最高優(yōu)先級(jí),1表示最低優(yōu)先級(jí),5是普通優(yōu)先級(jí)。
  • 優(yōu)先級(jí)最高的線程在執(zhí)行時(shí)被給予優(yōu)先。但是不能保證線程在啟動(dòng)時(shí)就進(jìn)入運(yùn)行狀態(tài)。
  • 與在線程池中等待運(yùn)行機(jī)會(huì)的線程相比,當(dāng)前正在運(yùn)行的線程可能總是擁有更高的優(yōu)先級(jí)。
  • t.setPriority()用來(lái)設(shè)定線程的優(yōu)先級(jí)。
  • 在線程開(kāi)始方法被調(diào)用之前,線程的優(yōu)先級(jí)應(yīng)該被設(shè)定。
  • 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITYNORM_PRIORITY來(lái)設(shè)定優(yōu)先級(jí)
  /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

線程的使用

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("t1 begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1 end");
    }
});

t1.start();

線程中特殊函數(shù)

join()

join方法是一個(gè)屬于對(duì)象的方法,主要作用是是的調(diào)用join方法的這個(gè)線程對(duì)象先執(zhí)行,調(diào)用方法所在的線程等執(zhí)行完了,在執(zhí)行。

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("t1 begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1 end");
    }
});

t1.start();
t1.join();

Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("t2 begin");
        System.out.println("t2 end");
    }
});
t2.start();

輸出的結(jié)果:


//注釋t1.join()


t1 begin

t2 begin

t2 end

t1 end

//沒(méi)有注釋t1.join()


t1 begin

t1 end

t2 begin

t2 end

wait()

表示等待獲取某個(gè)鎖執(zhí)行了該方法的線程釋放對(duì)象的鎖,JVM會(huì)把該線程放到對(duì)象的等待池中。該線程等待其它線程喚醒 notify() 執(zhí)行該方法的線程喚醒在對(duì)象的等待池中等待的一個(gè)線程,JVM從對(duì)象的等待池中隨機(jī)選擇一個(gè)線程,把它轉(zhuǎn)到對(duì)象的鎖池中。使線程由阻塞隊(duì)列進(jìn)入就緒狀態(tài)(只能在同步代碼塊中使用)上面尤其要注意一點(diǎn),一個(gè)線程被喚醒不代表立即獲取了對(duì)象的monitor,只有monitor,只有等調(diào)用完notify()或者notifyAll()并退出synchronized塊,釋放對(duì)象鎖后,其余線程才可獲得鎖執(zhí)行

sleep()

是一個(gè)類的方法,讓當(dāng)前線程停止執(zhí)行,讓出cpu給其他的線程,但是不會(huì)釋放對(duì)象鎖資源以及監(jiān)控的狀態(tài),當(dāng)指定的時(shí)間到了之后又會(huì)自動(dòng)恢復(fù)運(yùn)行狀態(tài)。有一個(gè)用法可以代替yield函數(shù)——sleep(0)

yield()

這方法與sleep()類似,可以使用sleep(0)來(lái)達(dá)到相同的效果,只是不能由用戶指定暫停多長(zhǎng)時(shí)間,并且yield()方法只能讓同優(yōu)先級(jí)或者高優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì),注意這里并不是一定,有可能又會(huì)執(zhí)行當(dāng)前線程,執(zhí)行完后,這個(gè)線程的狀態(tài)從執(zhí)行狀態(tài)轉(zhuǎn)到了就緒狀態(tài)。

notify()

執(zhí)行該方法的線程喚醒在對(duì)象的等待池中等待的一個(gè)線程,JVM從對(duì)象的等待池中隨機(jī)選擇一個(gè)線程,把它轉(zhuǎn)到對(duì)象的鎖池中。使線程由阻塞隊(duì)列進(jìn)入就緒狀態(tài)。注意:這里必須持有相同鎖的線程

interrupt()

中斷線程,被中斷線程會(huì)拋InterruptedException

線程的停止

當(dāng)線程啟動(dòng)時(shí),我們?cè)趺慈ネV箚?dòng)的線程呢?一般來(lái)說(shuō),有

run()和start()的區(qū)別

我們從源碼來(lái)學(xué)習(xí),這兩個(gè)方法的不同,Thread類的方法:


    /**
     * Package-scope method invoked by Dalvik VM to create "internal"
     * threads or attach threads created externally.
     *
     * Don't call Thread.currentThread(), since there may not be such
     * a thing (e.g. for Main).
     */
    Thread(ThreadGroup group, String name, int priority, boolean daemon) {
        synchronized (Thread.class) {
            id = ++Thread.count;
        }
        if (name == null) {
            this.name = "Thread-" + id;
        } else {
            this.name = name;
        }
        if (group == null) {
            throw new InternalError("group == null");
        }
        this.group = group;
        this.target = null;
        this.stackSize = 0;
        this.priority = priority;
        this.daemon = daemon;
        /* add ourselves to our ThreadGroup of choice */
        this.group.addThread(this);
    }
    /**
     * Initializes a new, existing Thread object with a runnable object,
     * the given name and belonging to the ThreadGroup passed as parameter.
     * This is the method that the several public constructors delegate their
     * work to.
     *
     * @param group ThreadGroup to which the new Thread will belong
     * @param runnable a java.lang.Runnable whose method <code>run</code> will
     *        be executed by the new Thread
     * @param threadName Name for the Thread being created
     * @param stackSize Platform dependent stack size
     * @throws IllegalThreadStateException if <code>group.destroy()</code> has
     *         already been done
     * @see java.lang.ThreadGroup
     * @see java.lang.Runnable
     */
//帶runnable參數(shù)的thread類的構(gòu)造函數(shù)調(diào)用了這個(gè)方法
    private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
        Thread currentThread = Thread.currentThread();
        if (group == null) {
            group = currentThread.getThreadGroup();
        }
        if (group.isDestroyed()) {
            throw new IllegalThreadStateException("Group already destroyed");
        }
        this.group = group;
        synchronized (Thread.class) {
            id = ++Thread.count;
        }
        if (threadName == null) {
            this.name = "Thread-" + id;
        } else {
            this.name = threadName;
        }
        //建立的runnable接口賦值給thread中的target
        this.target = runnable;
        this.stackSize = stackSize;
        this.priority = currentThread.getPriority();
        this.contextClassLoader = currentThread.contextClassLoader;
        // Transfer over InheritableThreadLocals.
        if (currentThread.inheritableValues != null) {
            inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
        }
        // add ourselves to our ThreadGroup of choice
        this.group.addThread(this);
    }

run方法的源代碼:


 public void run() {
        if (target != null) {
            target.run();
        }
    }

在run方法中,直接調(diào)用的是我們傳入的target(Runnable對(duì)象)的run方法,并沒(méi)有開(kāi)啟新的線程

start方法的源代碼:


 public synchronized void start() {
        checkNotStarted();
        hasBeenStarted = true;
        nativeCreate(this, stackSize, daemon);
    }

start方法最后調(diào)用了nativeCreate的native方法,這個(gè)方法的主要作用是開(kāi)啟了一個(gè)新的線程。并且這個(gè)方法,會(huì)利用jni回調(diào)Thread的run方法。

總結(jié):

  1. 如果直接調(diào)用run方法,并沒(méi)有開(kāi)啟新的線程,而是直接運(yùn)行run方法里面的內(nèi)容,
  2. 而start方法,則會(huì)調(diào)用native方法 nativeCreate 開(kāi)啟線程

線程的停止

實(shí)際開(kāi)發(fā)中,我們使用線程的場(chǎng)景一般是執(zhí)行耗時(shí)任務(wù),如果我們開(kāi)啟了多個(gè)新的線程來(lái)執(zhí)行新的任務(wù),最后又不在對(duì)他進(jìn)行關(guān)閉,這樣有時(shí)候會(huì)浪費(fèi)資源和內(nèi)存的泄露。那我們?cè)趺磥?lái)管理我們的線程呢?目前有兩種方法:

  • 我們自己手動(dòng)開(kāi)發(fā),管理我們的線程,包括線程的啟動(dòng),線程的回收, 線程的停止等
  • 使用JDK中自帶的線程池技術(shù)

今天我們不講線程池,后面的文章會(huì)講到。對(duì)于單個(gè)線程而言,上面我們將了他的啟動(dòng),現(xiàn)在我們來(lái)講他的關(guān)閉。

線程的關(guān)閉的二種方式:

1. 使用標(biāo)志位

我們定義一個(gè)標(biāo)志位,在線程的run方法中,不斷的循環(huán)檢測(cè)標(biāo)志位,從而確定是否退出

public class ShutdownThread extends Thread {  
    public volatile boolean exit = false;   
        public void run() {   
        while (!exit){  
            //do something  
        }  
    }   
}  
2. 使用interrupt方法

這里可以分為兩種情況:

  • 線程處于阻塞狀態(tài),如使用了sleep,同步鎖的wait,socket的receiver,accept等方法時(shí),會(huì)使線程處于阻塞狀態(tài)。當(dāng)調(diào)用線程的interrupt()方法時(shí),系統(tǒng)會(huì)拋出一個(gè)InterruptedException異常,代碼中通過(guò)捕獲異常,然后break跳出循環(huán)狀態(tài),使線程正常結(jié)束。通常很多人認(rèn)為只要調(diào)用interrupt方法線程就會(huì)結(jié)束,實(shí)際上是錯(cuò)的,一定要先捕獲InterruptedException異常之后通過(guò)break來(lái)跳出循環(huán),才能正常結(jié)束run方法。
public class ShutdownThread extends Thread {  
    public void run() {   
        while (true){  
            try{  
                    Thread.sleep(5*1000);阻塞5妙  
                }catch(InterruptedException e){  
                    e.printStackTrace();  
                    break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)。  
                }  
        }  
    }   
}   
  • 線程未進(jìn)入阻塞狀態(tài),使用isInterrupted()判斷線程的中斷標(biāo)志來(lái)退出循環(huán),當(dāng)使用interrupt()方法時(shí),中斷標(biāo)志就會(huì)置true,和使用自定義的標(biāo)志來(lái)控制循環(huán)是一樣的道理。
public class ShutdownThread extends Thread {  
    public void run() {   
        while (!isInterrupted()){  
            //do something, but no tthrow InterruptedException  
        }  
    }   
}  

為什么要區(qū)分進(jìn)入阻塞狀態(tài)和和非阻塞狀態(tài)兩種情況了,是因?yàn)楫?dāng)阻塞狀態(tài)時(shí),如果有interrupt()發(fā)生,系統(tǒng)除了會(huì)拋出InterruptedException異常外,還會(huì)調(diào)用interrupted()函數(shù),調(diào)用時(shí)能獲取到中斷狀態(tài)是true的狀態(tài),調(diào)用完之后會(huì)復(fù)位中斷狀態(tài)為false,所以異常拋出之后通過(guò)isInterrupted()是獲取不到中斷狀態(tài)是true的狀態(tài),從而不能退出循環(huán),因此在線程未進(jìn)入阻塞的代碼段時(shí)是可以通過(guò)isInterrupted()來(lái)判斷中斷是否發(fā)生來(lái)控制循環(huán),在進(jìn)入阻塞狀態(tài)后要通過(guò)捕獲異常來(lái)退出循環(huán)。

因此使用interrupt()來(lái)退出線程的最好的方式應(yīng)該是兩種情況都要考慮:

public class ThreadSafe extends Thread {  
    public void run() {   
        while (!isInterrupted()){ //非阻塞過(guò)程中通過(guò)判斷中斷標(biāo)志來(lái)退出  
            try{  
                Thread.sleep(5*1000);//阻塞過(guò)程捕獲中斷異常來(lái)退出  
            }catch(InterruptedException e){  
                e.printStackTrace();  
                break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)。  
            }  
        }  
    }   
}   
最后編輯于
?著作權(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)容

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