簡介
java中多線程的開發中少不了使用Thread,我們在使用Thread中提供的API過程中,應該注意些什麼規則呢?
一起來看一看吧。
start一個Thread
Thread中有兩個方法,一個是start方法,一個是run方法,兩個都可以調用,那麼兩個有什麼差別呢?
先看一下start方法:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
start()是一個synchronized的方法,通過它會去調用native的start0方法,而最終将會調用Thread的run()方法。
我們知道,建立一個Thread有兩種方式,一種是傳入一個Runnable,一個是繼承Thread,并重寫run()方法。
如果我們直接調用Thread的run()方法會發生什麼事情呢?
先看一下run方法的定義:
public void run() {
if (target != null) {
target.run();
}
}
預設情況下, 這個target就是一個Runnable對象,如果Thread是通過Runnable來建構的話,調用Thread.run()會在目前線程中運作run方法中的内容。
如果Thread是以其形式建構,并且沒有重新run()方法,那麼直接調用Thread.run()将什麼都不會做。
public void wrongStart(){
Runnable runnable= ()-> System.out.println("in thread running!");
Thread thread= new Thread(runnable);
thread.run();
}
public void correctStart(){
Runnable runnable= ()-> System.out.println("in thread running!");
Thread thread= new Thread(runnable);
thread.start();
}
是以,上面兩種調用方式,隻有第二種是正确的。
不要使用ThreadGroup
Thread中有個字段類型是java.lang.ThreadGroup,這個主要是用來給Thread進行分組,我們看下Thread的這個構造函數:
public Thread(ThreadGroup group, Runnable target) {
this(group, target, "Thread-" + nextThreadNum(), 0);
}
上面的構造函數可以在傳入runnable的同時傳遞一個ThreadGroup對Thread進行分組。
如果沒有指定ThreadGroup,那麼将會為其配置設定一個預設的default group。
ThreadGroup是做什麼的呢?ThreadGroup是java 1.0引入的方法,主要是一次性的對一組thread進行操作。我們可以調用ThreadGroup.interrupt()來一次性的對整個Group的Thread進行interrupts操作。
雖然ThreadGroup提供了很多有用的方法,但是其中很多方法都被廢棄了,比如:allowThreadSuspension(), resume(), stop(), 和 suspend(),并且ThreadGroup中還有很多方法是非線程安全的:
- ThreadGroup.activeCount()
這個方法主要是用來統計一個ThreadGroup中活動的線程個數,這個方法會統計還未啟動的線程,同時也會受系統線程的影響,是以是不準确的。
- ThreadGroup.enumerate()
這個方法是将ThreadGroup和子group的線程拷貝到一個數組中,但是如果數組太小了,多餘的線程是會被自動忽略的。
ThreadGroup本身有一個 stop() 方法用來停止所有的線程,但是stop是不安全的,已經被廢棄了。
那麼我們該怎麼去安全的停止很多個線程呢?
使用executor.shutdown()就可以了。
不要使用stop()方法
剛剛講了ThreadGroup中不要調用stop()方法,因為stop是不安全的。
調用stop方法會立馬釋放線程持有的所有的鎖,并且會抛出ThreadDeath異常。
因為會釋放所有的鎖,是以可能會造成受這些鎖保護的對象的狀态發生不一緻的情況。
替代的方法有兩種,一種是使用volatile flag變量,來控制線程的循環執行:
private volatile boolean done = false;
public void shutDown(){
this.done= true;
}
public void stopWithFlag(){
Runnable runnable= ()->{
while(!done){
System.out.println("in Runnable");
}
};
Thread thread= new Thread(runnable);
thread.start();
shutDown();
}
另外一種方法就是調用interrupt(), 這裡我們要注意interrupt()的使用要點:
- 如果目前線程執行個體在調用Object類的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在該執行個體中調用了Thread.sleep(long)或Thread.sleep(long,int)方法,并且正在阻塞狀态中時,則其中斷狀态将被清除,并将收到InterruptedException。
- 如果此線程在InterruptibleChannel上的I/O操作中處于被阻塞狀态,則該channel将被關閉,該線程的中斷狀态将被設定為true,并且該線程将收到java.nio.channels.ClosedByInterruptException異常。
- 如果此線程在java.nio.channels.Selector中處于被被阻塞狀态,則将設定該線程的中斷狀态為true,并且它将立即從select操作中傳回。
- 如果上面的情況都不成立,則設定中斷狀态為true。
先看下面的例子:
public static void main(String[] args) {
Runnable runnable= ()->{
while (!Thread.interrupted()) {
System.out.println("in thread");
}
};
Thread thread= new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
我們在while循環中調用了Thread.interrupted()方法用來判斷線程是否被設定了中斷位,然後在main方法中調用了thread.interrupt()來設定中斷,最終可以正确的停止Thread。
注意,這裡運作的Thread并沒有被阻塞,是以并不滿足我們上面提到的第一個條件。
下面我們再看一個例子:
public static void main(String[] args) {
Runnable runnable= ()->{
while (!Thread.interrupted()) {
System.out.println("in thread");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread= new Thread(runnable);
thread.start();
thread.interrupt();
}
這個例子和上面的例子不同之處就是在于,Thread中調用了sleep方法,導緻Thread被阻塞了,最終滿足了第一個條件,進而不會設定終端位,隻會抛出InterruptedException,是以這個例子中線程是不會被停止的,大家一定要注意。
wait 和 await 需要放在循環中調用
為什麼要放在循環中呢?因為我們希望wait不是被錯誤的被喚醒,是以我們需要在wait被喚醒之後,重新檢測一遍條件。
錯誤的調用是放在if語句中:
synchronized (object) {
if (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
正确的方法是放在while循環中:
synchronized (object) {
while (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
本文的代碼:
learn-java-base-9-to-20/tree/master/security本文已收錄于 http://www.flydean.com/java-security-code-line-thread/最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!