線程的狀態(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)中。

線程的優(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é):
- 如果直接調(diào)用run方法,并沒有開啟新的線程,而是直接運行run方法里面的內(nèi)容,
- 而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)。
}
}
}
}