start() vs run()
Thread는 Runnable을 구현하고 있기 때문에 내부에 run() 메소드가 있고, 이 부분에 작업 내용을 작성하지만, 실제로 실행할 때는 start()를 통해 실행해야 제대로 동작한다. 그 이유에 대해서 알아보자.
public
class Thread implements Runnable {
/* ... 생략 ... */
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();
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
위와 같이 start() 함수를 호출하면, 내부적으로 start0()를 호출하고, run()의 경우 그냥 입력받은 target:Runnable의 run()을 실행한다. 즉, 우리가 Thread의 run()을 호출하면 어떠한 멀티쓰레딩 작업(콜스택 생성 등)도 진행하지 않고, 그냥 Runnable의 함수를 호출하는 꼴이다. 반면 start()를 호출하면 native method인 start0()를 호출한다.
#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},
{"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},
};
#undef THD
#undef OBJ
#undef STE
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
Java Native 코드인 thread.c이다. start0()은 JVM_StratThread라는 함수 매핑 되어 있다. 한글로 자세한 주석을 포함했다.
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
// nativeThread 객체 선언
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;
// 입력받은 쓰레드 객체가 null이 아니면 (이미 한번 실행했던 Thread라면) -> 예외발생
} 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);
// 쓰레드를 새로 생성
if (native_thread->osthread() != NULL) {
native_thread->prepare(jthread);
// 새로 생성한 객체가 null이 아니면 준비시킴
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
// 새로 생성한쓰레드가 null이면
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);
// 모든 준비가 끝나면 쓰레드 시작
JVM_END
JVM_StratThread는 위와 같다. start()를 실행한 쓰레드가 이미 한번 실행했던 쓰레드라면 예외를 발생시키고, 아니라면 할당할 콜스택의 크기를 계산하여 새로운 콜스택과 함께 쓰레드 객체를 새로 생성한다. 만약 콜스택의 크기가 충분하지 않다면 Out of memory 에러를 출력한다. 그리고 모든 준비가 끝나면 쓰레드를 시작한다.
'자바' 카테고리의 다른 글
자바 쓰레드 (5) - 쓰레드 제어 (1) (0) | 2020.04.19 |
---|---|
자바 쓰레드 (4) - 데몬 쓰레드 (Daemon Thread) (0) | 2020.04.19 |
자바 쓰레드 (3) - 쓰레드 그룹 (Thread Group) (0) | 2020.04.19 |
자바 쓰레드 (1) - 쓰레드란 무엇인가? (0) | 2020.04.19 |