Thread類的例項方法
start()
start方法內部會呼叫方法start方法啟動一個執行緒,該執行緒返回start方法,同時Java虛擬機器呼叫native start0啟動另一個執行緒呼叫run方法,此時有兩個執行緒並行執行;
我們來分析下start0方法,start0到底是如何呼叫run方法的
Thread類裡有一個本地方法叫registerNatives,此方法註冊一些本地方法給Thread類使用
在OpenJDK官網找到Thread.c
#include "jni.h"
#include "jvm.h"
#include "java_lang_Thread.h"
#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread}, //Java中Thread類的start方法所呼叫的start0方法
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};
......
根據關鍵字"JVM_StartThread"再找到jvm.cpp
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
{
MutexLocker mu(Threads_lock);
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz); //請看這裡,例項化了一個執行緒native_thread
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
}
}
sz是大小引數,忽略之,我們看thread_entry是什麼
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
obj,
KlassHandle(THREAD, SystemDictionary::Thread_klass()),
vmSymbols::run_method_name(), //請看這裡,jvm呼叫run_method_name方法
vmSymbols::void_method_signature(),
THREAD);
}
run_method_name在vmSymbols.hpp被定義
/* common method and field names */
template(run_method_name, "run") //run_method_name的名稱是"run"
簡言之:當前執行緒呼叫start方法通知ThreadGroup
當前執行緒可以執行了,可以被加入了,當前執行緒啟動後,當前執行緒狀態為"Runnable"。另一個執行緒等待CPU時間片,呼叫run方法(執行緒真正執行)。產生一個非同步執行的效果;
用start方法來啟動執行緒,真正實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢而直接繼續執行下面的程式碼。
程式碼如下
public class MyThread03 extends Thread{
public void run()
{
try
{
for (int i = 0; i < 3; i++)
{
Thread.sleep((int)(Math.random() * 1000));
System.out.println("run = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
MyThread03 mt = new MyThread03();
mt.start();
try
{
for (int i = 0; i < 3; i++)
{
Thread.sleep((int)(Math.random() * 1000));
System.out.println("run = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
執行結果如下,可以看到,Thead-0和main執行緒交叉執行,是無序的。很好理解,因為main和Thread-0在爭搶CPU資源,這個過程是無序的。
run = main
run = Thread-0
run = main
run = main
run = Thread-0
run = Thread-0
再看一個例子,程式碼如下
public class MyThread04 extends Thread{
public void run()
{
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args)
{
MyThread04 mt0 = new MyThread04();
MyThread04 mt1 = new MyThread04();
MyThread04 mt2 = new MyThread04();
mt0.start();
mt1.start();
mt2.start();
}
}
執行結果如下
Thread-0
Thread-2
Thread-1
我們依次啟動mt0,mt1,mt2,這說明執行緒啟動順序也是無序的。因為start方法僅僅返回撥用,執行緒想要執行必須得到CPU時間片再執行run方法,CPU時間片的獲得是無序的。
run()
run方法是Thread類的一個普通方法,執行run方法其實是單執行緒執行
public class MyThread05 extends Thread{
public void run()
{
System.out.println("run = " + Thread.currentThread().getName());
}
public static void main(String[] args)
{
MyThread05 mt = new MyThread05();
mt.run();
try
{
for (int i = 0; i < 3; i++)
{
Thread.sleep((int)(Math.random() * 1000));
System.out.println("run = " + Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
輸出結果如下
run = main
run = main
run = main
run = main
main執行緒迴圈了3次,run方法1次,結果是main執行緒執行了四次,我們寫在run方法體內的被main執行緒執行,這說明呼叫run方法執行多執行緒是不可行的。
isAlive()
判斷執行緒是否存活
public class MyThread06 extends Thread{
public void run()
{
System.out.println("run = " + this.isAlive());
}
public static void main(String[] args) throws Exception
{
MyThread06 mt = new MyThread06();
System.out.println("begin == " + mt.isAlive());
mt.start();
Thread.sleep(100);
System.out.println("end == " + mt.isAlive());
}
}
輸出結果如下,增加0.1秒延遲,讓執行緒執行完
begin == false
run = true
end == false
可以看到,執行前false,執行中true,執行後false
getId()
返回執行緒的識別符號,執行緒ID是正值,執行緒ID在生命週期內不會變化,當執行緒終止了,執行緒ID可能會被重用
getName()
返回執行緒名稱
getPriority()和setPriority(int)
返回優先順序和設定優先順序
優先順序越高的執行緒獲取CPU時間片的概率越高
請看如下的例子
public class MyThread07_0 extends Thread{
public void run()
{
System.out.println("MyThread07_0 run priority = " +
this.getPriority());
}
public static void main(String[] args)
{
System.out.println("main thread begin, priority = " +
Thread.currentThread().getPriority());
System.out.println("main thread end, priority = " +
Thread.currentThread().getPriority());
MyThread07_0 thread = new MyThread07_0();
thread.start();
}
}
執行結果如下
main thread begin, priority = 5
main thread end, priority = 5
MyThread07_0 run priority = 5
執行緒的預設優先順序是5
再看如下的例子
public class MyThread07_1 extends Thread {
public void run()
{
System.out.println("MyThread07_1 run priority = " +
this.getPriority());
MyThread07_0 thread = new MyThread07_0();
thread.start();
}
public static void main(String[] args)
{
System.out.println("main thread begin, priority = " +
Thread.currentThread().getPriority());
System.out.println("main thread end, priority = " +
Thread.currentThread().getPriority());
MyThread07_1 thread = new MyThread07_1();
thread.start();
}
}
我們在MyThread07_1執行緒內部啟動MyThread07_0執行緒,我們觀察MyThread07_1和MyThread07_0的優先順序有什麼關係。
執行結果如下
main thread begin, priority = 5
main thread end, priority = 5
MyThread07_1 run priority = 5
MyThread07_0 run priority = 5
MyThread07_0和MyThread07_1執行緒的優先順序一致,說明執行緒具有繼承性。
現在我們來設定優先順序
public class MyThread08 {
static class MyThread08_0 extends Thread {
public void run() {
long beginTime = System.currentTimeMillis();
for (int j = 0; j < 1000000; j++) {}
long endTime = System.currentTimeMillis();
System.out.println("★★★★ MyThread08_0 use time = " +
(endTime - beginTime));
}
}
static class MyThread08_1 extends Thread {
public void run()
{
long beginTime = System.currentTimeMillis();
for (int j = 0; j < 1000000; j++){}
long endTime = System.currentTimeMillis();
System.out.println("☆☆☆☆ MyThread08_1 use time = " +
(endTime - beginTime));
}
}
public static void main(String[] args)
{
for (int i = 0; i < 5; i++)
{
MyThread08_0 mt0 = new MyThread08_0();
mt0.setPriority(5);
mt0.start();
MyThread08_1 mt1 = new MyThread08_1();
mt1.setPriority(4);
mt1.start();
}
}
}
我們給MyThread08_0執行緒設定更高的優先順序5
執行結果如下
★★★★ MyThread08_0 use time = 7
☆☆☆☆ MyThread08_1 use time = 4
★★★★ MyThread08_0 use time = 18
★★★★ MyThread08_0 use time = 16
★★★★ MyThread08_0 use time = 20
★★★★ MyThread08_0 use time = 17
☆☆☆☆ MyThread08_1 use time = 0
☆☆☆☆ MyThread08_1 use time = 10
☆☆☆☆ MyThread08_1 use time = 9
☆☆☆☆ MyThread08_1 use time = 8
可以看到MyThread08_0先執行的次數更多,輸出結果為實心五角星的這個。
多執行幾次,都會是MyThread08_0先列印完,每次結果都不盡相同,CPU會盡量先讓MyThread08_0執行完。
isDaemon()和setDaemon(boolean)
isDaemon方法判斷是否是守護執行緒;
setDaemon設定守護執行緒
在Java中有兩類執行緒:User Thread(使用者執行緒)、Daemon Thread(守護執行緒)
我們自定義的執行緒和main執行緒都是使用者執行緒,我們熟知的GC(垃圾回收器)就是守護執行緒。守護執行緒是使用者執行緒的“奴僕”,當使用者執行緒執行完畢,守護執行緒就會終止,因為它沒有存在的必要了。
如使用者執行緒執行結束,GC無垃圾可回收,它只能死亡
看如下程式碼
public class MyThread09 extends Thread{
private int i = 0;
public void run()
{
try
{
while (true)
{
i++;
System.out.println(Thread.currentThread().getName()+" i = " + i);
Thread.sleep(1000);
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
try
{
MyThread09 mt = new MyThread09();
mt.setDaemon(true);
mt.start();
Thread.sleep(5000);
System.out.println("現在是"+Thread.currentThread().getName()+"執行緒");
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
我們自定義MyThread09執行緒的run方法裡是死迴圈,如果是使用者執行緒,它應該永遠地執行下去,現在把它設定成守護執行緒。
注意:mt.setDaemon(true);要在mt.start();之前,見
否則會丟擲IllegalThreadStateException異常
執行結果如下
Thread-0 i = 1
Thread-0 i = 2
Thread-0 i = 3
Thread-0 i = 4
Thread-0 i = 5
現在是main執行緒
Thread-0 i = 6
MyThread09變成了守護執行緒,它的使命已經完成。現在是main執行緒
Thread.sleep(5000)的目的是使main執行緒沉睡5s,即使用者執行緒(main執行緒)仍在執行,此時main執行緒輸出,再沉睡1ms,當main執行緒執行完畢,守護執行緒就沒有存在的意義了,即死亡;
main執行緒總共執行了大約5001ms(略大於這個數值),Thread-0列印到i=6,說明守護執行緒在main執行緒之後死亡,這個時間差極小
interrupt()
設定中斷標誌位,無法中斷執行緒
public class MyThread10 extends Thread{
public void run()
{
for (int i = 0; i < 500000; i++)
{
System.out.println("i = " + (i + 1));
}
}
public static void main(String[] args)
{
try
{
MyThread10 mt = new MyThread10();
mt.start();
Thread.sleep(2000);
mt.interrupt();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
輸出結果如下
......
i = 499993
i = 499994
i = 499995
i = 499996
i = 499997
i = 499998
i = 499999
i = 500000
可以看到,interrupt()沒有中斷執行緒,interrupt()後續將會詳細講解
isInterrupted()
判斷執行緒是否被中斷
join()
等待這個執行緒死亡,舉例說明:
執行緒A執行join方法,會阻塞執行緒B,執行緒A join方法執行完畢,才能執行執行緒B
程式碼如下
public class MyThread11 extends Thread{
public void run()
{
try
{
int secondValue = (int)(Math.random() * 1000);
System.out.println(secondValue);
Thread.sleep(secondValue);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception
{
MyThread11 mt = new MyThread11();
mt.start();
mt.join();
System.out.println("MyThread11執行完畢之後我再執行");
}
}
輸出結果如下
75
MyThread11執行完畢之後我再執行
可以看到,main執行緒在mt執行緒之後執行。mt呼叫join方法,使main執行緒阻塞,待mt執行緒執行完畢,方可執行main執行緒。
Thread類的靜態方法
currentThread()
返回當前正在執行執行緒的引用
public class MyThread12 extends Thread{
static
{
System.out.println("靜態塊的列印:" +
Thread.currentThread().getName());
}
public MyThread12()
{
System.out.println("構造方法的列印:" +
Thread.currentThread().getName());
}
public void run()
{
System.out.println("run()方法的列印:" +
Thread.currentThread().getName());
}
public static void main(String[] args)
{
MyThread12 mt = new MyThread12();
mt.start();
}
}
輸出結果
靜態塊的列印:main
構造方法的列印:main
run()方法的列印:Thread-0
可以看到,構造方法和靜態塊是main執行緒在呼叫,重寫的run方法是執行緒自己在呼叫。
再看個例子
public class MyThread13 extends Thread{
public MyThread13()
{
System.out.println("MyThread13----->Begin");
System.out.println("Thread.currentThread().getName()----->" +
Thread.currentThread().getName());
System.out.println("this.getName()----->" + this.getName());
System.out.println("MyThread13----->end");
}
public void run()
{
System.out.println("run----->Begin");
System.out.println("Thread.currentThread().getName()----->" +
Thread.currentThread().getName());
System.out.println("this.getName()----->" + this.getName());
System.out.println("run----->end");
}
public static void main(String[] args)
{
MyThread13 mt = new MyThread13();
mt.start();
}
}
輸出結果
MyThread13----->Begin
Thread.currentThread().getName()----->main
this.getName()----->Thread-0
MyThread13----->end
run----->Begin
Thread.currentThread().getName()----->Thread-0
this.getName()----->Thread-0
run----->end
可以看到,執行MyThread13構造方法的執行緒是main,執行MyThread13的執行緒是Thread-0(當前執行緒),run方法就是被執行緒例項所執行。
sleep(long)
讓當前執行緒沉睡若干毫秒
public class MyThread14 extends Thread{
public void run()
{
try
{
System.out.println("run threadName = " +
this.getName() + " begin");
Thread.sleep(2000);
System.out.println("run threadName = " +
this.getName() + " end");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
MyThread14 mt = new MyThread14();
mt.start();
}
}
輸出結果如下
run threadName = Thread-0 begin
run threadName = Thread-0 end
列印完第一句兩秒後列印第二句。
yield()
當前執行緒放棄CPU的使用權,這裡的放棄是指當前執行緒少用CPU資源,最後執行緒還是會執行完成
public class MyThread15 extends Thread {
public void run()
{
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 5000000; i++)
{
Thread.yield();
count = count + i + 1;
}
long endTime = System.currentTimeMillis();
System.out.println("用時:" + (endTime - beginTime) + "毫秒!");
}
public static void main(String[] args)
{
MyThread15 mt = new MyThread15();
mt.start();
}
}
輸出結果如下
用時:4210毫秒!
可以看到,任務執行完畢,當我們把Thread.yield();註釋掉,執行時間只需要7ms。說明當前執行緒放棄了一些CPU資源。
interrupted()
判斷當前執行緒是否中斷,靜態版的isInterrupted方法。多執行緒中斷機制,後續會詳細解析。