Java線程基礎(chǔ)知識

線程的狀態(tài)

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

線程的優(yōu)先級

  • 當(dāng)線程的優(yōu)先級沒有指定時,所有線程都攜帶普通優(yōu)先級。
  • 優(yōu)先級可以用從1到10的范圍指定。10表示最高優(yōu)先級,1表示最低優(yōu)先級,5是普通優(yōu)先級。
  • 優(yōu)先級最高的線程在執(zhí)行時被給予優(yōu)先。但是不能保證線程在啟動時就進入運行狀態(tài)。
  • 與在線程池中等待運行機會的線程相比,當(dāng)前正在運行的線程可能總是擁有更高的優(yōu)先級。
  • t.setPriority()用來設(shè)定線程的優(yōu)先級。
  • 在線程開始方法被調(diào)用之前,線程的優(yōu)先級應(yīng)該被設(shè)定。
  • 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來設(shè)定優(yōu)先級
  /**
     * 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方法是一個屬于對象的方法,主要作用是是的調(diào)用join方法的這個線程對象先執(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

//沒有注釋t1.join()


t1 begin

t1 end

t2 begin

t2 end

wait()

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

sleep()

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

yield()

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

notify()

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

interrupt()

中斷線程,被中斷線程會拋InterruptedException

線程的停止

當(dāng)線程啟動時,我們怎么去停止啟動的線程呢?一般來說,有

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

我們從源碼來學(xué)習(xí),這兩個方法的不同,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)用了這個方法
    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對象)的run方法,并沒有開啟新的線程

start方法的源代碼:


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

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

總結(jié):

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

線程的停止

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

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

今天我們不講線程池,后面的文章會講到。對于單個線程而言,上面我們將了他的啟動,現(xiàn)在我們來講他的關(guān)閉。

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

1. 使用標志位

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

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等方法時,會使線程處于阻塞狀態(tài)。當(dāng)調(diào)用線程的interrupt()方法時,系統(tǒng)會拋出一個InterruptedException異常,代碼中通過捕獲異常,然后break跳出循環(huán)狀態(tài),使線程正常結(jié)束。通常很多人認為只要調(diào)用interrupt方法線程就會結(jié)束,實際上是錯的,一定要先捕獲InterruptedException異常之后通過break來跳出循環(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)。  
                }  
        }  
    }   
}   
  • 線程未進入阻塞狀態(tài),使用isInterrupted()判斷線程的中斷標志來退出循環(huán),當(dāng)使用interrupt()方法時,中斷標志就會置true,和使用自定義的標志來控制循環(huán)是一樣的道理。
public class ShutdownThread extends Thread {  
    public void run() {   
        while (!isInterrupted()){  
            //do something, but no tthrow InterruptedException  
        }  
    }   
}  

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

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

public class ThreadSafe extends Thread {  
    public void run() {   
        while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標志來退出  
            try{  
                Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出  
            }catch(InterruptedException e){  
                e.printStackTrace();  
                break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)。  
            }  
        }  
    }   
}   
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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