最近发现对于线程还不是很熟悉只是停于理解,便对Thread.java类做一个系统的研究,并记录在此。
先看如下三个例子:
@Test
public void test03() throws InterruptedException {
new Thread(new MyRunnable()).start();
new MyThread().start();
new Thread(new FutureTask<>(new MyCallable())).start();
}
public static
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("I am MyRunnable.");
}
}
public static
class MyThread extends Thread {
@Override
public void run() {
System.out.println("I am MyThread.");
}
}
public static
class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
System.out.println("I am MyCallable.");
return null;
}
}
执行结果
从案例分析,我们可以了解到任何线程的实现方式都离不开Thread.java,由此可以该类的重要性(ps:除非自己再去构造一个Thread.java)。从某种意义上来讲,线程的实现方式有三种,分别是:
仔细观察以上三个接口,我们就会发现FutureTask.java其本质是实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口,所以其本质上是Runnable的一个子集。为什么会有这个接口呢?原因很简单,我们需要该方法的返回值。
我们先从Thread.java的执行顺序来分析,首先需要注意的是Thread.java中有一个静态代码块,如下:
private static native void registerNatives();
static {
registerNatives();
}
注意该方法标记了native,意思就是该方法会通过JNI调用本地方法,例如C/C++的方法。registerNatives()方法主要的作用就是注册一些本地方法供Thread.java使用,如start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的。这个方法放在一个static语句块中,当该类被加载到JVM中的时候,它就会被调用,进而注册相应的本地方法。而本地方法registerNatives()是定义在Thread.c文件中的。
Thread.c是个很小的文件,它定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如下:
JNIEXPORT void JNICALL
Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ //registerNatives
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
static JNINativeMethod methods[] = {
{"start0", "()V",(void *)&JVM_StartThread}, //start0 方法
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, //stop0 方法
{"isAlive","()Z",(void *)&JVM_IsThreadAlive}, //isAlive 方法
{"suspend0","()V",(void *)&JVM_SuspendThread}, //suspend0 方法
{"resume0","()V",(void *)&JVM_ResumeThread}, //resume0 方法
{"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, //setPriority0 方法
{"yield", "()V",(void *)&JVM_Yield}, //yield 方法
{"sleep","(J)V",(void *)&JVM_Sleep}, //sleep 方法
{"currentThread","()" THD,(void *)&JVM_CurrentThread}, //currentThread 方法
{"countStackFrames","()I",(void *)&JVM_CountStackFrames}, //countStackFrames 方法
{"interrupt0","()V",(void *)&JVM_Interrupt}, //interrupt0 方法
{"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, //isInterrupted 方法
{"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, //holdsLock 方法
{"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, //getThreads 方法
{"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, //dumpThreads 方法
};
Java_Java_lang_Thread_registerNatives()方法是JNI的命名方式,通过约束就可以准确找到并调用本地方法。methods[] 数组,每一行的参数意思如下:
有兴趣的同学可以去了解一下JNI,JNI PDF著名书籍下载,阅读后对你理解一些比较底层方法的源码有帮助。然后,我们再看其构造函数。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}
对于上述参数的含义,如下:
从Thread.java的所有构造函数中,我们可以清晰的发现,所有的构造函数都调用了init()方法。对于构造函数调用实例方法init(),有必要说明一下:
类体中的方法分为实例方法和类方法两种,用static修饰的是类方法。
当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址,当该类创建对象后,类中的实例方法才分配入口地址,从而实例方法可以被类创建的任何对象调用执行。需要注意的是,当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址,也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
对于类中的类方法,在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出才被取消。类方法在类的字节码加载到内存时就分配了入口地址。
因此,Java语言允许通过类名直接调用类方法,而实例方法不能通过类名调用。在Java语言中,类中的类方法不可以操作实例变量,也不可以调用实例方法,这是因为在类创建对象之前,实例成员变量还没有分配内存,而且实例方法也没有入口地址。
实例对象的构造顺序是 实例变量>实例代码块>构造函数,所以在构造函数中使用实例方法init()是可以的。init()代码如下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//检查线程的name是否为空,为空则抛出异常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//设置线程名字
this.name = name;
//返回对当前正在执行的线程对象的引用
Thread parent = currentThread();
//如果已经为当前应用程序建立了安全管理员,则返回该安全管理员; 否则返回null
SecurityManager security = System.getSecurityManager();
//检查线程组
if (g == null) {
//安全检查
if (security != null) {
//返回要在其被调用时实例化任何正在创建的新线程的线程组。 默认情况下,它返回当前线程的线程组
g = security.getThreadGroup();
}
//设置线程组
if (g == null) {
//当前正在执行的线程对象的线程组
g = parent.getThreadGroup();
}
}
//检查可达性,确定当前运行的线程是否有权限修改此线程组
g.checkAccess();
//是否有权限访问
if (security != null) {
//验证此实例(可能是子类)可以在不违反安全约束的情况下构造:子类必须不覆盖对安全敏感的非最终方法,
//否则检查“enableContextClassLoaderOverride”运行时权限。
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//往线程组添加线程但未启动
g.addUnstarted();
//设置线程组
this.group = g;
//设置是否是守护线程
this.daemon = parent.isDaemon();
//设置优先级,默认继承父线程的优先级
this.priority = parent.getPriority();
//设置线程上下文加载器
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
//获取当前调用上下文的“快照”
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
//设置目标对象
this.target = target;
//设置优先级,优先级覆盖父线程的优先级
setPriority(priority);
//设置InheritableThreadLocal,提供从父线程到子线程的值继承:
//当创建子线程时,子线程将接收父线程具有值的所有可继承线程局部变量的初值。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//存储指定的堆栈大小
this.stackSize = stackSize;
//设置线程ID
tid = nextThreadID();
}
初始化完成后,就需要显示调用start()方法,让其进入准备状态。start()方法如下:
public synchronized void start() {
//如果不是准备状态,抛出异常,即一个线程只能使用一次start()方法
if (threadStatus != 0)
throw new IllegalThreadStateException();
//添加进线程组
group.add(this);
//开始标记
boolean started = false;
try {
//调用native方法
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 */
}
}
}
private native void start0();
上述代码,配上注释很容易理解。其中start0()方法是会调用本地方法,在本文最开始,我们发现
{"start0", "()V",(void *)&JVM_StartThread}
JVM_StartThread方法将会被Java调用,在 jvm.cpp 中,我们发现:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) {
JavaThread *native_thread = NULL;
...
native_thread = new JavaThread(&thread_entry, sz);
...
Thread::start(native_thread);
}
在hotspot\src\share\vm\runtime\thread.cpp中查看JavaThread对象的实现:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : ...
{
...
os::create_thread(this, thr_type, stack_sz);
}
void Thread::start(Thread* thread)
{
...
os::start_thread(thread);
}
在hotspot\src\os目录下可以看到windows, linux, solaris和posix的实现,先检查linux\vm\os_linux.cpp:
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size)
{
...
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
...
}
通过调用平台的API创建一个线程!在回到 jvm.cpp 中,代码段:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)){
...
native_thread = new JavaThread(&thread_entry, sz);
...
}
这里JVM_ENTRY()是一个宏,用来定义JVM_StartThread()函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 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()),
vmSymbolHandles::run_method_name(), //LOOK! 看这里
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到调用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定义的:
class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") //LOOK!!! 这里决定了调用的方法名称是 “run”!
...
}
就是这里进行调用了Thread.java类中的run方法。而Thread.java类的run方法如下:
public void run() {
if (target != null) {
target.run();
}
}
很明显,该方法又去调用target对象的run方法。至此我们理解了一个完整的Thread的创建以及运行过程。后面我将贴出比较重要的一些方法,有兴趣可以阅读源码。
//返回当前正在运行的线程
public static native Thread currentThread();
//向调度程序发出的提示,表明当前线程愿意使用当前的处理器。调度程序可以忽略这个提示
public static native void yield();
//使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性
public static native void sleep(long millis) throws InterruptedException;
//测试该线程是否为活动线程。如果线程已启动但尚未死亡,则该线程为活动线程
public final native boolean isAlive();
//当且仅当当前线程持有指定对象上的监视器锁时,返回true
public static native boolean holdsLock(Object obj);
值得一提的是Thread.java优先级范围是1-10。如下:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
当线程执行完任务时,如何销毁呢?答案在exit()方法中:
private void exit() {
if (group != null) {
//通知组线程该线程已经终止。
group.threadTerminated(this);
group = null;
}
target = null;
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
上述代码,全部对象设置为空,可以使JVM垃圾回收器有效的进行回收,减少内存使用量。再来看看join()方法,需要提前说明的是Object.java对象的wait()方法,wait()方法使当前线程等待,直到另一个线程调用
notify()或notifyAll()方法或已经过了指定的时间(就算wait的传参值)。join()方法如下:
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
//直到该线程死亡才结束
while (isAlive()) {
wait(0);
}
} else {
//直到超过预期时间
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
对于线程异常的捕获可以通过setUncaughtExceptionHandler()方法来控制,如下:
//设置在此线程因未捕获异常而突然终止时调用的处理程序
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
学如逆水行舟,不进则退。
Java线程的创建
Java Thread:揭开Run方法被调用的真正面纱