文章目录
- 前言
- 状态获取之-与运算
- 总结
前言
Thread是java并行场景实现中经常用到的类,它会在主线程运行同时再开启新的线程,两个或者多个线程同时运行,以提高程序的执行效率。应用到实际项目中可以大大提升响应时间,提升用户体验。
我们都知道java线程有的状态有六种:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,这六种状态在Thread中被定义为枚举,我这两天通过看java的源码以及JVM的部分源码发现它的状态变更的设计,不是说线程该到哪个值就把状态set成对应的枚举,下面我们来看下他的实现逻辑。
状态获取之-与运算
这里先给出答案-----就是与运算实现的。
下面根据源码一步一步的分析一下
public class ThreadStateExample {
static class ThreadDemo extends Thread{
@Override
public void run() {
System.out.println("hello world");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new ThreadDemo());
thread.start();
thread.getState();
}
}
如上代码,是我通过继承Thread类方式实例了一个Thread对象。通过代码可以看到thread有一个getState()的方法,见名知意,他是获取此线程对象的当前状态的,我也试过写thread.setState()的方法调用,但是Thread中没有这个方法,显然线程状态的变更不可能通过直接set的方式。那么我接着就去看了一下getState()的源码,我把相关的代码贴到下方
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
/**
* Returns the state of this thread.
* This method is designed for use in monitoring of the system state,
* not for synchronization control.
*
* @return this thread's state.
* @since 1.5
*/
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
toThreadState(threadStatus)方法
public static Thread.State toThreadState(int var0) {
if ((var0 & 4) != 0) {
return State.RUNNABLE;
} else if ((var0 & 1024) != 0) {
return State.BLOCKED;
} else if ((var0 & 16) != 0) {
return State.WAITING;
} else if ((var0 & 32) != 0) {
return State.TIMED_WAITING;
} else if ((var0 & 2) != 0) {
return State.TERMINATED;
} else {
return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
}
}
Thread类先初始化了一个threadStatus的属性,然后通过对此属性的与运输得到对应的线程状态。
那么现在有个问题:这个threadStatus的属性,在Thread类中既没有set方法,也没有get方法,他是如何变更的呢?
在解决这个问题之前,我们来想一下,java的线程什么时候会涉及到状态的变更,这应该是个容易的问题吧。
有几个场景,比如:Thread.start()、Object.sleep(long)、Object().wait(long)以及锁等待等等。而通过我扒源码发现这些方法都是有native关键字来修饰,也就是他们最终都是调用到JVM底层也就是通过C来实现的。这里有几点需要明白
- 在 Java 应用程序中,调用 Thread.start() 方法启动新线程。
- Java 虚拟机将 start() 方法调用转换为本地方法调用,并将参数传递给本地方法 start0()。
- 在 start0() 方法中,Java 虚拟机会创建一个新线程并将其绑定到一个操作系统线程。
- Java 虚拟机会设置新线程的执行入口为 Java 线程的 run() 方法,并将 Java 线程的 Thread 对象与操作系统线程关联起来。
- 当操作系统线程启动后,它会从 run() 方法的代码开始执行,并在执行完 run() 方法后退出。
- 在 Java 应用程序中,线程的状态会从 NEW 状态变为 RUNNABLE 状态,并且线程的 run() 方法会在新线程中被执行。
下面我来跟下start()方法的源码,来看下线程状态是如何发生变更的。start方法中调用了本地方法
private native void start0();
java中凡是用了native关键字修饰的类,都会有一个XXX.c的文件(java jni的函数库)与之对应,那么我找到JDK8的源码中Thread.c的文件,粘出相关代码如下;
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对应c的JVM_StartThread函数,这里就不再说c的文件怎么找了,大家可以自己研究一下,而JVM_StartThread函数调用链路涉及jvm.cpp、thread.cpp两个文件,粘出相关代码如下;
jvm.cpp
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
//。。。函数实现太长,我这里省略其他代码,只看相关代码
Thread::start(native_thread);
JVM_END
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);
}
}
通过上面源码可以看到,线程状态的变更最终是通过java_lang_Thread::set_thread_status函数实现的,那么接着来看java_lang_Thread::RUNNABLE枚举值是什么,它是在javaClass.hpp文件中被定义的。粘出相关代码如下:
javaClass.hpp
enum ThreadStatus {
NEW = 0,
RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running
JVMTI_THREAD_STATE_RUNNABLE,
SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_SLEEPING,
IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long)
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
JVMTI_THREAD_STATE_PARKED,
PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long)
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_PARKED,
BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
TERMINATED = JVMTI_THREAD_STATE_TERMINATED
};
到这里心里渐渐明朗,这些状态应该是几个常量值加起来之后得到一个数值。
等式右边这些枚举值是在jvmti.h文件中被定义的。
jvmti.h
enum {
JVMTI_THREAD_STATE_ALIVE = 0x0001,
JVMTI_THREAD_STATE_TERMINATED = 0x0002,
JVMTI_THREAD_STATE_RUNNABLE = 0x0004,
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400,
JVMTI_THREAD_STATE_WAITING = 0x0080,
JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010,
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020,
JVMTI_THREAD_STATE_SLEEPING = 0x0040,
JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100,
JVMTI_THREAD_STATE_PARKED = 0x0200,
JVMTI_THREAD_STATE_SUSPENDED = 0x100000,
JVMTI_THREAD_STATE_INTERRUPTED = 0x200000,
JVMTI_THREAD_STATE_IN_NATIVE = 0x400000,
JVMTI_THREAD_STATE_VENDOR_1 = 0x10000000,
JVMTI_THREAD_STATE_VENDOR_2 = 0x20000000,
JVMTI_THREAD_STATE_VENDOR_3 = 0x40000000
};
这些值都是十六进制,那么我们算一下RUNNABLE状态值是多少。
RUNNABLE = JVMTI_THREAD_STATE_ALIVE + JVMTI_THREAD_STATE_RUNNABLE = 5;
JVMTI_THREAD_STATE_ALIVE = 0x0001 = 1;
JVMTI_THREAD_STATE_RUNNABLE = 0x0004 = 4;
上文中我们提到jvm创建的线程要和java线程对象关联,而这个状态值关联的就是threadStatus的属性。
最后根据java源码的与运输逻辑计算得出结果:
5 & 4
0101 & 0100 = 0100 =4
所以线程状态:return State.RUNNABLE;
总结
具体来说,Java 线程的状态是由多个状态标志位组合而成的,在获取线程状态时,通过与运算获取线程状态,会对这些二进制位进行操作,从而简单高效的得到线程的状态信息。