-
如何创建一个线程。
有两种基本的方式,一是继承Thread类,并重写run方法;二是实现runnable或callable接口,然后创建一个thread,并以该接口作为参数。
如果使用Executors框架,可以分离线程的管理和任务的执行,传递一个runnable或callable接口启动一个线程:
ExecutorService service = Executors.newCachedThreadPool();
service.execute(runnable);
-
请从JDK和JVM的视角描述线程启动的详细过程。
调用Thread类的start方法启动一个线程,需注意调用线程的run方法或者runnable或callable接口的run方法并不能启动线程,run方法的调用和普通的blocking方法调用无任何区别。从JVM角度看线程的启动过程有一些复杂,详细过程如下:
-
通过Thread类的start方法调用native start0方法;
public synchronized void start() {
…boolean started = false;
try {
start0();
started = true;
} finally {
…
}
}
-
通过JNI,start0方法调用JVM_StartThread,该方法负责创建并启动一个kernel线程,因为每一个java线程对应一个kernel线程。
Jvm.cpp:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
…
native_thread = new JavaThread(&thread_entry, sz);
…
Thread::start(native_thread);
JVM_END
-
new一个JavaThread对象会创建一个kernel线程,代码如下所示:
Os_bsd.cpp:
bool os::create_thread(Thread* thread, ThreadType thr_type,
size_t req_stack_size) {
…
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
…
}
-
创建kernel线程时,传入的thread_native_entry方法负责启动所有的java线程,该方法会一直阻塞直到新创建的kernel线程状态设置为runnable,代码如下:
static void *thread_native_entry(Thread *thread) {
…
{
MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
osthread->set_state(INITIALIZED);
sync->notify_all();
while (osthread->get_state() == INITIALIZED) {
sync->wait(Mutex::_no_safepoint_check_flag);
}
}
thread->run();
…
}
-
而启动kernel线程的方法参见b)中的Thread::start(native_thread),该方法会设置kernel线程状态为runnable,如下所示:
os.cpp
void os::start_thread(Thread* thread) {
…
OSThread* osthread = thread->osthread();
osthread->set_state(RUNNABLE);
pd_start_thread(thread);
}
f. 当kernel线程设置为runnable状态后,thread_native_entry方法调用 thread->run(),该方法调用JavaThread类的run方法,如下:
// The first routine called by a new Java thread
void JavaThread::run() {
// initialize thread-local alloc buffer related fields
this->initialize_tlab();
// used to test validity of stack trace backs
this->record_base_of_stack_pointer();
// Record real stack base and size.
this->record_stack_base_and_size();
this->create_stack_guard_pages();
this->cache_global_variables();
…
thread_main_inner();
…
}
g.thread_main_inner方法调用entry_point方法,代码如下:
void JavaThread::thread_main_inner() {
…
this->entry_point()(this, this);
}
…
}
h.entry_point()方法指向thread_entry,其中包括了JavaCalls::call_virtual,该方法即为JVM调用java方法的入口,代码如下:
static void thread_entry(JavaThread* thread, TRAPS) {
…
JavaCalls::call_virtual(&result,
obj,
KlassHandle(THREAD, SystemDictionary::Thread_klass()),
vmSymbols::run_method_name(),
vmSymbols::void_method_signature(),
THREAD);
}
自此,java thread启动成功。由此可见,thread的start方法间接调用执行了run方法,直接是由JVM来调用的。
-
请描述线程的状态机。
线程状态机如下图所示:
引自https://www.uml-diagrams.org/examples/java-6-thread-state-machine-diagram-example.htm
需注意java的线程状态和内核状态不能一一对应,比如:java线程的NEW状态就对应了内核线程的allocated,initialized两种状态。内核线程的状态有:
enum ThreadState {
ALLOCATED, // Memory has been allocated but not initialized
INITIALIZED, // The thread has been initialized but yet started
RUNNABLE, // Has been started and is runnable, but not necessarily running MONITOR_WAIT, // Waiting on a contended monitor lock
CONDVAR_WAIT, // Waiting on a condition variable
OBJECT_WAIT, // Waiting on an Object.wait() call
BREAKPOINTED, // Suspended at breakpoint
SLEEPING, // Thread.sleep()
ZOMBIE // All done, but not reclaimed yet
};
java线程的状态有:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
上述状态的字段说明在JVM和JDK源码中已经详细给出可参考Thread.cpp和Thread.java。有一点需注意:线程的状态和线程是否拥有锁的区别,例如线程处于WAITING状态时,线程可能拥有锁(调用sleep方法)也可能已经释放锁(调用wait方法)。
-
请说出wait/notify(notifyAll)和 await/signal(signalAll)之间的异同。
-
Wait/notify是对象方法, 而await/signal是Condition接口方法。
-
优先使用await/signal,因为Condition接口把监视器方法与对象分离开,可以在一个Lock上绑定多个await/signal。但对于内置锁(intrinsic lock),即synchronized绑定的监视器,只有一个“Condition”可用。
-
两者都支持time参数,wait/await(milliseconds)、wait/await(milliseconds, nanos)。而await除了上述方法,还支持awaitUntil(Date)。
-
在wait和await之前调用Notify和signal时,信号都会丢失。
总之,在Lock代替synchronized的地方,使用Condition代替对象监视器方法。
-
请说出sleep和yield的异同
相同点:sleep, yield都是线程方法。如果当前线程持有锁,两者都会回释放锁。事实上,根据java规范,sleep和yield没有任何线程同步相关的作用。
不同点:当前线程调用sleep方法后,线程处于waiting或者timed waiting;而yield只是暗示JVM当前线程想要休息一会,不做其他任何事情,由JVM的线程调度器决定如何做下一步,因此当前线程仍处于runnable状态,只不过不是running,而是ready。Sleep方法执行完后,线程可能进入runnable或者终结;而yield结束后,线程仍处于runnable。Sleep会抛出InterruptedException,而yield不会。