1 java創建線程的幾種方式
1.1 實現Runnable接口。
public class RunnableDemo implements Runnable{
public static void main(String[] args) {
//寫法1
new Thread(new RunnableDemo(), "runnable線程1").start();
//寫法2
new Thread(new Runnable() {
public void run() {
System.out.println("當前線程:" + Thread.currentThread().getName());
}
}, "runnable線程2").start();
}
public void run() {
System.out.println("當前線程:" + Thread.currentThread().getName());
}
}
1.2 繼承Thread類,重寫run方法。
public class ThreadDemo extends Thread{
public ThreadDemo(String name){
super.setName(name);
}
public static void main(String[] args) {
//寫法1:
new ThreadDemo("thread線程1").start();
//寫法二:
new Thread("thread線程2"){
@Override
public void run() {
System.out.println("當前線程:" + Thread.currentThread().getName());
}
}.start();
}
@Override
public void run() {
System.out.println("當前線程:" + Thread.currentThread().getName());
}
}
其實Thread類,也是實現了Runnable接口,重寫了run方法,默認的run方法的邏輯:
public void run() {
if (target != null) {
target.run();
}
}
也就是,如果有target,就調用target的run,如果沒有target,就什麼也不做(當然,如果重新了thread的run,就會調用子類的run)。這個target是第一種創建線程方式(實現Runnable接口)傳入的Runnable。
1.3 Callable/Future
帶返回值
1.4 ThreadPool
實際中不會手動new Thread,因爲手動創建的線程不可控,而是使用線程池
2 線程狀態變更
2.1 狀態變更圖
2.2 查看線程狀態
可以通過 jstack 命令查看線程的狀態,如,有如下程序:
public class ThreadStatusDemo implements Runnable{
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "線程1").start();
new Thread(new Runnable() {
public void run() {
synchronized (this){
try {
wait(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "線程2").start();
new Thread(new Runnable() {
public void run() {
synchronized (this){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "線程3").start();
new Thread(new Runnable() {
public void run() {
while (true){
System.out.println(123344);
}
}
}, "線程4").start();
ThreadStatusDemo demo = new ThreadStatusDemo();
new Thread(demo, "線程5").start();
new Thread(demo, "線程6").start();
}
public void run() {
synchronized (this){
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
啓動程序後,首先通過jps查看java進程端口號:
D:\projects\thread>jps
11424 Launcher
1332
10120 KotlinCompileDaemon
10172 Jps
10572 ThreadStatusDemo
再通過jstack查看如下:
D:\projects\thread>jstack 10572
2019-12-01 14:35:52
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode):
"DestroyJavaVM" #20 prio=5 os_prio=0 tid=0x0000000002cae800 nid=0x38a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"線程6" #19 prio=5 os_prio=0 tid=0x000000001ecc8800 nid=0x2650 waiting on condition [0x000000002018f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bxp.ThreadStatusDemo.run(ThreadStatusDemo.java:57)
- locked <0x00000006c1c06178> (a com.bxp.ThreadStatusDemo)
at java.lang.Thread.run(Thread.java:748)
"線程5" #18 prio=5 os_prio=0 tid=0x000000001ecc8000 nid=0x3478 waiting for monitor entry [0x000000002008e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.bxp.ThreadStatusDemo.run(ThreadStatusDemo.java:57)
- waiting to lock <0x00000006c1c06178> (a com.bxp.ThreadStatusDemo)
at java.lang.Thread.run(Thread.java:748)
"線程4" #17 prio=5 os_prio=0 tid=0x000000001ecc5000 nid=0x3ed4 runnable [0x000000001ff8e000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x00000006c1c0c090> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x00000006c1c0c070> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x00000006c1c0c030> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.newLine(PrintStream.java:546)
- eliminated <0x00000006c1c0c070> (a java.io.PrintStream)
at java.io.PrintStream.println(PrintStream.java:737)
- locked <0x00000006c1c0c070> (a java.io.PrintStream)
at com.bxp.ThreadStatusDemo$4.run(ThreadStatusDemo.java:42)
at java.lang.Thread.run(Thread.java:748)
"線??3" #16 prio=5 os_prio=0 tid=0x000000001ecbf800 nid=0x1630 in Object.wait() [0x000000001fe8f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000006c1c06188> (a com.bxp.ThreadStatusDemo$3)
at java.lang.Object.wait(Object.java:502)
at com.bxp.ThreadStatusDemo$3.run(ThreadStatusDemo.java:31)
- locked <0x00000006c1c06188> (a com.bxp.ThreadStatusDemo$3)
at java.lang.Thread.run(Thread.java:748)
"線程2" #15 prio=5 os_prio=0 tid=0x000000001ecbe800 nid=0x1ee8 in Object.wait() [0x000000001fd8f000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000006c1c0a000> (a com.bxp.ThreadStatusDemo$2)
at com.bxp.ThreadStatusDemo$2.run(ThreadStatusDemo.java:19)
- locked <0x00000006c1c0a000> (a com.bxp.ThreadStatusDemo$2)
at java.lang.Thread.run(Thread.java:748)
"線程1" #14 prio=5 os_prio=0 tid=0x000000001ecbd800 nid=0x250c waiting on condition [0x000000001fc8f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bxp.ThreadStatusDemo$1.run(ThreadStatusDemo.java:8)
at java.lang.Thread.run(Thread.java:748)
3 線程啓動,爲什麼調用的是start方法。
如果調用run方法,run方式只是一個普通的實例方法。我們並不是想簡單的執行run,而是希望能夠啓動一個線程,並且讓這個新啓動的線程去調用run。
start方法,作用就是創建一個線程,並且線程回調run方法。
可以跟一下Thread的start方法源碼,發現start中調用了start0,而start0是一個本地方法:
private native void start0();
我們可以下載hotspot源碼,查看start0的具體實現。可以通過Thread.c去找到java線程相關的本地方法和hotspot源碼的對應方法,如下:
從上圖中可以看到,start0對應的hotspot中的方法爲JVM_StartThread。JVM_StartThread方法在jvm.cpp文件中。
可以看到,JVM_StartThread中通過如下代碼創建了一個JavaThread
native_thread = new JavaThread(&thread_entry, sz);
而new JavaThread實現在thread.cpp中,如下:
os::create_thread(this, thr_type, stack_sz);
可以看出,是通過調用當前操作系統的方法,創建了一個線程,所以最終的創建線程,實際是通過操作系統來實現的。
創建好線程後,最後通過調用Thread::start啓動線程
Thread::start(native_thread);
Thread::start在thread.cpp中的具體實現如下:
void Thread::start(Thread* thread) {
trace("start", thread);
// Start is different from resume in that its safety is guaranteed by context or
// being called from a Java method synchronized on the Thread object.
if (!DisableStartThread) {
if (thread->is_Java_thread()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
os::start_thread(thread);
}
}
可以看出,是通過操作系統啓動了線程。並且在啓動前,設置了線程的狀態爲RUNNABLE。
4 爲什麼線程的stop等方法不建議使用?線程的終止方式?
stop方法,只需要持有線程的對象,就可以調用。也就是可以在任意線程中去終止一個線程,這就相當於在linux下通過kill -9 強制終止一個線程類似,對於線程來說是不安全的,因爲對於當前線程來說,被終止是不可知的。以及suspend(掛起),resume(恢復)等方法不建議使用也是同樣的。
4.1 通過自定義標誌位的方式終止線程
public class ThreadInterruptDemo {
public static boolean isInterrupt = false;
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
int i = 0;
while (!isInterrupt){
System.out.println("i = " + i ++);
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isInterrupt = true;
}
}
如上,通過控制isInterrupt 的值,可以達到終止線程的效果。
4.2 通過thread自帶的interrupt()的方式終止:
public class ThreadInterruptDemo2 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted()) {
System.out.println("i = " + i++);
}
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
這種方式,可以看到,其實思想和我們第一種方式自定義標誌位是類似的,只是這裏標誌位是線程定義,中斷方法也是線程自己提供的interrupt。 thread.interrupt(),就是把Thread.currentThread().isInterrupted()設置成true。可以看一下thread.interrupt()的實現,發現調用的是本地方法:
private native void interrupt0();
根據映射,發現實際調用的是hotspot中的JVM_Interrupt
繼續跟源碼,發現調用的是操作系統的interrupt:
void Thread::interrupt(Thread* thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt(thread);
}
以linux爲例,找一下源碼:
如上可以看到,最終就是設置interrupted的值爲true。
set_interrupted方法定義:
volatile jint _interrupted; // Thread.isInterrupted state
void set_interrupted(bool z) { _interrupted = z ? 1 : 0; }
對於isInterrupted,同樣的方式,最終在hotspot中的實現:
interrupted()實現爲:
volatile bool interrupted() const { return _interrupted != 0; }
4.3 InterruptedException中斷
上面的兩種方式,都是在程序不斷的判斷標誌位的時候,通過控制標誌位來中斷線程,但是如果現在在執行過程中出現了wait,sleep,join的時候,我們該如何中斷線程呢?
這種情況下,也可以通過thread.interrupt();進行中斷,只是執行後並不會立即中斷線程的執行,而是拋出一箇中斷異常InterruptedException,收到這個中斷異常後,我們就可以在catch中自行處理了,此時是否中斷還是由當前線程說了算。
如下程序,執行後,會發現程序會繼續執行,並不會真正被中斷:
public class ThreadInterruptDemo4 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted()){
synchronized (this){
try {
System.out.println("i = " + i);
wait();
} catch (InterruptedException e) {
System.out.println("接收到中斷異常");
e.printStackTrace();
}
}
i++;
}
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
上面程序拋出中斷異常信號,但是不會被真正中斷,只會拋出中斷異常信號,最終是否中斷,還是取決於線程本身,如果想中斷,可以再收到中斷異常後做相應的中斷處理。
5 線程的復位重置
5.1 通過Thread.interrupetd方法對線程進行復位
public class ThreadInterruptDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("復位前:" + Thread.currentThread().isInterrupted());
Thread.interrupted();//復位
System.out.println("復位後:" + Thread.currentThread().isInterrupted());
}
}
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
從執行上面的代碼中,輸出true,false。中斷後,Thread.currentThread().isInterrupted()變爲true,通過Thread.interrupted()復位後,Thread.currentThread().isInterrupted()重新變爲false。
5.2 通過InterruptException復位
在線程的中斷方式中說過,處於阻塞狀態的線程,通過interrupt中斷,中斷後並不能立即終止線程,只是會拋出中斷異常,之所以沒有立即中斷,就是因爲在中斷後,又進行了復位操作。
可以看一下源碼:
上圖中,在sleep中,當同時滿足兩個條件的時候,拋出異常。
首先是Thread::is_interrupted (THREAD, true) == true,Thread::is_interrupted實現如下:
bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
bool interrupted = osthread->interrupted();
if (interrupted && clear_interrupted) {
osthread->set_interrupted(false);
// consider thread->_SleepEvent->reset() ... optional optimization
}
return interrupted;
}
首先獲得 osthread->interrupted(),因爲調用了interrupt觸發了中斷,所以此時osthread->interrupted()爲true,然後判斷如果interrupted == true並且clear_interrupted(這個應該是是否重置線程的標誌,傳入的爲true)爲true,然後重置了線程的狀態,變爲了false。最後返回true。
第二就是HAS_PENDING_EXCEPTION,字面意思是:是否有掛起的異常,這個變量在源碼中沒有找到在哪兒賦值的,暫且認爲和理解中斷無關,認爲是false吧。
這樣,那就是如果發現線程被中斷了,就會把線程狀態復位並且會拋出異常。
6 爲什麼所有的阻塞方法都會拋出InterruptException異常
因爲對於阻塞方法來說,如sleep, wait,join等,阻塞後,想要讓線程繼續執行,都有相對應的機制或者達到一定的條件,如sleep爲時間,wait和join爲notify或者notifyAll。而如果在阻塞中未達到條件的情況下,通過thread.interrupt進行中斷,對於線程本身來說是不正常的情況,所以這種情況下不會中斷線程,但是又不能什麼都不做,所以這個時候就會拋出中斷異常,線程本身收到中斷異常信號後,是否中斷由線程自身控制。
7 如果線程wait了,就會釋放CPU佔用,只有notify或者notifyAll的時候纔會重新搶佔CPU,但是調用interrupt的時候,線程會重新執行,所以interrupt中斷的時候,是否是喚醒了線程(線程處於sleep狀態下同樣)。
7.1 wait
先看一下interrupt的源碼如下:
如上圖,首先是設置線程中斷狀態爲true,然後通過unpark接觸了當前線程的阻塞狀態。
7.2 sleep
sleep不是阻塞,也沒有釋放cpu的佔用權,那sleep是怎麼做的呢,這就需要看一下sleep的實現:
如上圖,sleep原理大致就是一個死循環,然後對時間進行減減,當時間 <= 0的時候,就填出循環。當然,如果線程中斷狀態爲true的時候,也會跳出循環,根據前面講的,當跳出循環後,發現當前線程被中斷了,就會復位線程狀態並且拋出中斷異常。