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


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

Tips

  • Java及JVM源码基于Java 11
  • JVM源码仅展示关键内容,另附Open JDK链接
  • 文末附Java方法使用Demo的Gitee地址

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::start方法在thread.cpp中
  • os::create_thread方法在os_linux.cpp中,注意操作系统的区别
  • os::pd_start_thread方法在os_linux.cpp中,注意操作系统的区别
  • os::start_thread方法在os.cpp中

Thread.sleep和Object.wait

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

  • Object.wait
  • Thread.sleep

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

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.notify
  • Object.notifyAll

有了之前的经验,很容易想到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中调用?
  • 为什么wait方法设计在Object类中,而不是Thread类中?

首先,我们已经知道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可能会唤醒不止一个符合条件的线程。

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294965.html

(0)
上一篇 2022年12月25日
下一篇 2022年12月26日

相关推荐

发表回复

登录后才能评论