其他分享
首页 > 其他分享> > 03.关于线程你必须知道的8个问题(中)

03.关于线程你必须知道的8个问题(中)

作者:互联网

我们一起学习了如何创建线程,以及Java中线程状态,那么今天就来学习Thread类的核心方法。

Tips

Thread.start和Thread.run

上一篇中我们已经知道,Thread.run实际上是来自Runnable接口,直接调用并不会启动新线程,只会在主线程中运行。

Thread.start方法中调用的Thread.start0方法是真正承载了创建线程,调用Thread.run方法的能力

其实到这里已经回答了它们之间的区别,接下来我们一起来看底层是如何实现的。

Tips:有面向对象编程语言基础的,看懂JVM源码对你来说并不困难。

首先是thread.c文件,该文件为Java中Thread类注册了native方法。

static JNINativeMethod methods[] = {
    {"start0",          "()V",        (void *)&JVM_StartThread},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",           "(J)V",       (void *)&JVM_Sleep},
    {"interrupt0",     "()V",        (void *)&JVM_Interrupt}
};

Tips:native方法是Java Native Interface,简称JNI。

第一眼就可以看到start0对应的JVM方法JVM_StartThread,实现是在jvm.cpp中:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
	if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
		throw_illegal_thread_state = true;
	} else {
		// 创建虚拟机层面的线程
		native_thread = new JavaThread(&thread_entry, sz);
	}
	
	Thread::start(native_thread);
JVM_END

接着来看new JavaThread做了什么,在thread.cpp中:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
	os::create_thread(this, thr_type, stack_sz);
}

os::create_thread创建了操作系统层面的线程。这和上一篇中得到的结论是一致的,Java中的Thread.start0完成了操作系统层面线程的创建和启动

个人认为Thread.runThread.start是没什么可比性的。如果被问到这个问题,要么是面试官懒,网上随便找找就来问,要么是技术水平确实一般。

Tips

Thread.sleep和Object.wait

接下来看两个可以放在一起比较的方法:

很明显的区别是,它们并不在同一个类中定义,其次方法名上也能看出些许差别,“等待”和“睡眠”。

Object.wait

Java在Object类中,提供了2个wait方法的重载,不过最终都是调用JNI方法:

public final native void wait(long timeoutMillis) throws InterruptedException;

方法声明中我们能得知该方法的作用--使线程暂停指定的时间

接着我们来看Object.wait的方法注释:

Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.

使当前线程阻塞,直到主动唤醒或者超过指定时间。清晰的说明了Object.wait的功能,另外也提示了如何唤醒线程:

有了之前的经验,很容易想到Object.wait方法是在Object.c中注册的。我们找到它在jvm.cpp中的实现:

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
	ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

接着是ObjectSynchronizer::wait,在synchronizer.cpp中:

int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_wait);
  monitor->wait(millis, true, THREAD);
  return dtrace_waited_probe(monitor, obj, THREAD);
}

获取ObjectMonitor对象时,调用了ObjectSynchronizer::inflate方法,inflate翻译过来是膨胀的意思,是锁膨胀的过程。实际上,在未展示的代码中,还有偏向锁的过程,不过这些不是这部分的重点。

然后调用ObjectMonitor.wait,这个方法有225行,只看想要的部分:

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
	// 获取当前线程
	Thread * const Self = THREAD;
	// 添加到等待队列中
	AddWaiter(&node);
	// 退出监视器
	exit(true, Self);
	// 对等待时间的处理
	if (millis <= 0) {
		Self->_ParkEvent->park();
	} else {
		ret = Self->_ParkEvent->park(millis);
	}
}

答案已经呼之欲出了,ObjectMonitor.wait中退出了监视器,在Java层面就是Object.wait方法会释放监视器锁

对不同等待时间的处理也需要关注一下,millis <= 0的情况下,执行的是Self->_ParkEvent->park(),除非主动唤醒,否则线程永远停在这里。在Java层面看,执行object.wait(0)会使当前线程永久阻塞

既然都到这了,就多说一句,ObjectMonitor.exit中有几行关键代码,是synchronized特性实现的关键:

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
	for (;;) {
		if (Knob_ExitPolicy == 0) {
			OrderAccess::release_store(&_owner, (void*)NULL);
			OrderAccess::storeload();
		}
	}
}

这些内容我们提前混个眼熟,后面在synchronized中详细解释。

我们来思考两个问题:

首先,我们已经知道Object.wait的底层实现中,要释放监视器锁,释放的前提是什么?要先拥有监视器锁。那么在synchronized中调用Object.wait就很容易理解了。

其次,锁住的是什么?是对象,从来都不是执行线程(Thread实例是线程对象,不是执行线程)。因此涉及到监视器锁操作的方法是不是放到Object中更合适呢?

最后,如果你仔细阅读过Object.wait所有重载方法注释的话,你会发现一个词:spurious wakeup(虚假唤醒)

这是没有主动notify/notifyAll,或者被动中断,超时的情况下就唤醒处于WAITING状态的线程。因此Java也建议你在循环中调用Object.wait

synchronized (obj) {
	while (<condition does not hold> and <timeout not exceeded>) {
	long timeoutMillis = ... ; // recompute timeout values
	int nanos = ... ;
	obj.wait(timeoutMillis, nanos);
  }
  ...// Perform action appropriate to condition or timeout
}

简单解释下虚假唤醒产生的原因,我们已经知道Object.wait最终是通过Self->_ParkEvent->park()Self->_ParkEvent->park(millis)实现线程暂停的,其调用的park方法位于os_posix.cpp中:

void os::PlatformEvent::park() {
	status = pthread_cond_wait(_cond, _mutex);
}

int os::PlatformEvent::park(jlong millis) {
	status = pthread_cond_timedwait(_cond, _mutex, &abst);
}

pthread_cond_waitpthread_cond_timedwait是Linux对POSIX的实现,知道其作用即可,就不继续深入了。

我们很容易联想到,Object.notify的底层实现是调用os::PlatformEvent::unpark方法完成的。不出所料,从Object.c到ObjectMonitor.cpp,最后会发现该方法包含在os_posix.cpp中:

void os::PlatformEvent::unpark() {
	status = pthread_cond_signal(_cond);
}

同样的,pthread_cond_signal也是Linux对POSIX的实现。Linux man page中对其的解释是:

The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.
The pthread_cond_signal_() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).

其中第二段是关键,即pthread_cond_signal唤醒至少一个阻塞在指定条件上的线程。也就是说,调用Object.notify可能会唤醒不止一个符合条件的线程。

标签:Java,jdk,语言,运行环境,javac,高级语言,JVM,编程,创建线程
来源: