在進入文章主題之前我們先來說一下Thread和Runnable是什麼關係
一、Thread和Runnable是什麼關係
- Thread是一個類,Runnable是一個接口
public interface Runnable {
// 這裏面就一個抽象方法
public abstract void run();
}
- Thread實現了Runnable接口,使得run支持多線程
- 因類的單一繼承原則,推薦多使用Runnable接口
我們進行一個測試,分別是直接繼承Thread類和實現Runnable接口
1、繼承Thread類
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 17:24
*/
public class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run(){
for (int i = 0; i < 10 ; i ++) {
System.out.println("Thread start :" + this.name + ", i = " + i);
}
}
}
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 17:30
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread mtl1 = new MyThread("Thread1");
MyThread mtl2 = new MyThread("Thread2");
MyThread mtl3 = new MyThread("Thread3");
mtl1.start();
mtl2.start();
mtl3.start();
}
}
執行結果:
我們可以看到結果有2有1有3,順序是不定的,如果順序是1、2、3,可以多執行幾次,畢竟可能執行速度過快,出現上邊的亂序則說明,實現了多線程,在線程1沒有執行完的時候,執行了線程2或者是3。
接下來測試Runnable:
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 17:34
*/
public class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10 ; i ++) {
System.out.println("Thread start :" + this.name + ", i = " + i);
}
}
}
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 17:36
*/
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable("Runnable1");
MyRunnable mr2 = new MyRunnable("Runnable2");
MyRunnable mr3 = new MyRunnable("Runnable3");
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
Thread t3 = new Thread(mr3);
t1.start();
t2.start();
t3.start();
}
}
解釋一下這裏爲什麼還要新建Thread 對象,因爲Runnable只是個接口,只有一個run抽象方法,單單靠這些沒有辦法實現多線程的,Thread中有一個public Thread(Runnable target)
構造函數,可以傳入Ruannable實例來實現多線程
結果如下:
二、線程start方法和run方法的區別
通過上邊的介紹,想必對run()方法有一些認識了,一般它被我們用來在線程中執行我們的業務邏輯,也稱線程體。
那麼start()呢?start()是用來啓動一個線程的,執行該方法之後,線程就會處於就緒狀態(可執行狀態)。附上一張線程狀態圖:
那麼這兩個方法有什麼區別呢?
- 像前邊說的一個是啓動線程,一個是執行業務代碼的
- 調用start()方法會創建一個新的子線程並啓動
- run()方法只是Thread的一個普通方法的調用,還是會在當前線程下調用執行
對此我們可以測試一下:
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 16:27
*/
public class ThreadTest {
private static void attack() {
System.out.println("Hello");
System.out.println("當前線程爲: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread(){
public void run(){
attack();
}
};
System.out.println("當前主線程爲 : " + Thread.currentThread().getName());
t.run();
}
}
// 運行結果
當前主線程爲 : main
Hello
當前線程爲: main
我們換成start()試試:
package com.mtli.thread;
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-05-03 16:27
*/
public class ThreadTest {
private static void attack() {
System.out.println("Hello");
System.out.println("當前線程爲: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread(){
public void run(){
attack();
}
};
System.out.println("當前主線程爲 : " + Thread.currentThread().getName());
t.start();
}
}
// 運行結果
當前主線程爲 : main
Hello
當前線程爲: Thread-0
可以看到start()執行的時候是創建了一個新的線程然後啓動並調用方法,我們可以看看Thread源碼找到start():
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
在started = true之前有一個start0(),這個會新建一個線程,我們進入start0():
private native void start0();
可以看到是native方法,可以去openjdk查看:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f54e9b7c1036/src/share/native/java/lang/Thread.c
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"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},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
第一行就可以看到start0(),它調用的是JVM_StartThread方法,用於創建線程,引自於jvm.h,那我們去找一下jvm.cpp:http://hg.openjdk.java.net/jdk8u/hs-dev/hotspot/file/ae5624088d86/src/share/vm/prims
,在這裏有一個jvm.cpp:
在其中我們可以找到:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
// We cannot hold the Threads_lock when we throw an exception,
// due to rank ordering issues. Example: we might need to grab the
// Heap_lock while we construct the exception.
bool throw_illegal_thread_state = false;
// We must release the Threads_lock before we can post a jvmti event
// in Thread::start.
{
// Ensure that the C++ Thread and OSThread structures aren't freed before
// we operate.
MutexLocker mu(Threads_lock);
// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
// We could also check the stillborn flag to see if this thread was already stopped, but
// for historical reasons we let the thread detect that itself when it starts running
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
// Allocate the C++ Thread structure and create the native thread. The
// stack size retrieved from java is signed, but the constructor takes
// size_t (an unsigned type), so avoid passing negative values which would
// result in really large stacks.
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);
// At this point it may be possible that no osthread was created for the
// JavaThread due to lack of memory. Check for this situation and throw
// an exception if necessary. Eventually we may want to change this so
// that we only grab the lock if the thread was created successfully -
// then we can also do this check and throw the exception in the
// JavaThread constructor.
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
// No one should hold a reference to the 'native_thread'.
delete native_thread;
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
"unable to create new native thread");
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}
Thread::start(native_thread);
重點看這一句:
native_thread = new JavaThread(&thread_entry, 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(),
vmSymbols::void_method_signature(),
THREAD);
}
JavaCalls::call_virtual
它會call虛擬機然後去run我們這個新建的線程。具體start()即之後的流程如下:
以上純爲個人理解,如有不對,請各位看官及時指出(輕噴)