Linux操作系统环境下,Java线程和操作系统内核线程是一一对应的。 本文首先梳理操作系统线程模型基础知识, 进而给出Linux环境下Java线程与操作系统轻量级进程之间对应关系, 最后对Java线程状态转换以及其与Linux操作系统线程状态的对应关系进行简要总结。

为什么需要线程

  1. 在很多程序中, 需要多个线程互相同步活互斥的并行完成工作, 而将这些工作分解到不同的线程中去无疑简化了编程模型;

  2. 因为线程相比进程来说更加轻量, 所以线程的创建和销毁代价更小;

  3. 线程提高了性能, 虽然线程宏观上是并行的, 但是微观上确实串行的。 从CPU角度线程并无法提升性能, 但如果某些线程涉及到资源等待(如IO,等待输入等)时, 多线程允许进程中的其他线程继续执行而不是整个进程被阻塞, 因此提高了CPU利用率, 从这个角度会提升性能;

  4. 在多CPU或多核的情况下, 使用线程不仅仅在宏观上并行, 在微观上也是并行的。

需要注意的两点是:

  1. 进程有自己独立的内存地址空间, 而一个进程的多个线程共享该进程的内存地址空间;

  2. 进程是组织资源的最小单位, 而线程是CPU执行的最小单位。

操作系统线程模型

内核线程

内核线程(KLT: Kernel-Level Thread)就是直接由操作系统内核(Kernel)支持的线程, 这在处理异步事件如异步IO时特别有用。 内核线程的使用是廉价的, 唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。 支持多线程的内核叫做多线程内核(Multi-Threads kernal)。

内核线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情。

轻量级进程

轻量级进程(LWP: Light Weight Process)是一种由内核支持的用户线程, 它是基于内核线程的高级抽象, 因此只有先支持内核线程才可能有LWP。 每一个进程有一个或多个LWPs, 每个LWP由一个内核线程支持。这种模型实际上就是恐龙书上所提到的一对一线程模型。 在这种实现的操作系统中, LWP就是用户线程。

由于每个LWP都与一个特定的内核线程关联, 因此每个LWP都是一个独立的线程调度单元。 即使有一个LWP在系统调用中阻塞, 也不会影响整个进程的执行。

  • 优点: 程序员直接使用操作系统中已经实现的线程,而线程的创建、销毁、调度和维护,都是靠操作系统(准确的说是内核)来实现,程序员只需要使用系统调用,而不需要自己设计线程的调度算法和线程对CPU资源的抢占使用。

  • 缺点: 轻量级进程具有局限性。 首先, 大多数LWP的操作, 如建立、析构以及同步, 都需要进行系统调用。系统调用的代价相对较高: 需要在user mode和kernal mode中进行切换; 其次, 每个LWP都需要有一个内核线程支持, 因此LWP需要消耗内核资源(内核线程的栈空间)。 因此一个系统不能支持大量的LWP。

Linux中程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口–轻量级进程,轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。

PS:其实Linux中的pthread库就是调用了轻量级线程接口,来在操作系统中创建一个内核线程。

用户线程

LWP虽然本质上属于用户线程, 但LWP线程库是建立在内核之上的, LWP的许多操作都要进行系统调用, 因此效率不高。 而这里的用户线程(UT: User Thread)指的是完全建立在用户空间的线程库, 用户线程的建立,同步, 销毁, 调度完全在用户空间完成, 不需要内核的帮助。 因此这种线程的操作是极其快速的且低消耗的。

当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间实现。在操作系统看来,每一个进程只有一个线程。过去的操作系统大部分是这种实现方式,这种方式的好处之一就是即使操作系统不支持线程,也可以通过库函数来支持线程。

  • 优点:这样做有一些优点,首先就是确实在操作系统中实现了真实的多线程,其次就是线程的调度只是在用户态,减少了操作系统从内核态到用户态的切换开销。

  • 缺点:这种模式最致命的缺点也是由于操作系统不知道线程的存在,因此当一个进程中的某一个线程进行系统调用时,比如缺页中断而导致线程阻塞,此时操作系统会阻塞整个进程,即使这个进程中其它线程还在工作。还有一个问题是假如进程中一个线程长时间不释放CPU,因为用户空间并没有时钟中断机制,会导致此进程中的其它线程得不到CPU而持续等待。

混合模型

使用用户线程和LWP混合的线程管理模型下, 既存在用户线程也存在轻量级进程, 用户线程还是完全建立在用户空间中, 因此用户线程的创建、切换、析构等操作依然廉价, 并且可以支持大规模的用户线程并发。 而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁, 这样可以使用内核提供的线程调度功能及处理器映射, 并且用户线程的系统调用要通过轻量级进程来完成, 大大降低了整个进程被阻塞的风险。 在这种混合模式中, 用户线程与轻量级进程之间的数量比是不定的。 即为N:M。

PS:现在主流操的操作系统已经不太常用这种线程模型了

Java线程

映射到操作系统

Java线程在JDK1.2之前,是基于称为“绿色线程”(Green Threads)的用户线程实现的,而在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现。因此,在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对Java程序的编码和运行过程来说,这些差异都是透明的。

对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。

也就是说,现在的Java中线程的本质,其实就是操作系统中的线程,Linux下是基于pthread库实现的轻量级进程,Windows下是原生的系统Win32 API提供系统调用从而实现多线程。

接下来以JDK8的源码进行分析:

  • 创建并启动Java Thread
1
2
3
4
Thread t = new Thread(() -> {
System.out.println("Thread State When Running: " + Thread.currentThread().getState());
});
t.start();
  • start实现

JDK 1.8 Thread.java中Thread#start方法的实现, 实际上是通过Native调用start0方法实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();

/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);

boolean started = false;
try {
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 对应的JNI 实现

start0对应的JNI实现在JDK源码中的src/share/native/java/lang/Thread.c文件中定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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},
};
  • JVM_StartThread 进一步我们来看Thread.c中JVM_StartThread方法实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;

// We cannot hold the Threads_lock when we throw an exception,
// due to rank ordering issues. Example: we might need to grab the
// Heap_lock while we construct the exception.
bool throw_illegal_thread_state = false;

// We must release the Threads_lock before we can post a jvmti event
// in Thread::start.
{
// Ensure that the C++ Thread and OSThread structures aren't freed before
// we operate.
MutexLocker mu(Threads_lock);

// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
// We could also check the stillborn flag to see if this thread was already stopped, but
// for historical reasons we let the thread detect that itself when it starts running

jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
// Allocate the C++ Thread structure and create the native thread. The
// stack size retrieved from java is signed, but the constructor takes
// size_t (an unsigned type), so avoid passing negative values which would
// result in really large stacks.
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);

// At this point it may be possible that no osthread was created for the
// JavaThread due to lack of memory. Check for this situation and throw
// an exception if necessary. Eventually we may want to change this so
// that we only grab the lock if the thread was created successfully -
// then we can also do this check and throw the exception in the
// JavaThread constructor.
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
}
}

if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}

assert(native_thread != NULL, "Starting null thread?");

if (native_thread->osthread() == NULL) {
// No one should hold a reference to the 'native_thread'.
delete native_thread;
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
"unable to create new native thread");
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}

Thread::start(native_thread);

接下来我们关注

1
native_thread = new JavaThread(&thread_entry, sz);
  • JavaThread

实现在hotspot/src/share/vm/runtime/thread.cpp中, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
#if INCLUDE_ALL_GCS
, _satb_mark_queue(&_satb_mark_queue_set),
_dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
if (TraceThreadEvents) {
tty->print_cr("creating thread %p", this);
}
initialize();
_jni_attach_state = _not_attaching_via_jni;
set_entry_point(entry_point);
// Create the native thread itself.
// %note runtime_23
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread;
os::create_thread(this, thr_type, stack_sz);
_safepoint_visible = false;
// The _osthread may be NULL here because we ran out of memory (too many threads active).
// We need to throw and OutOfMemoryError - however we cannot do this here because the caller
// may hold a lock and all locks must be unlocked before throwing the exception (throwing
// the exception consists of creating the exception object & initializing it, initialization
// will leave the VM via a JavaCall and then all locks must be unlocked).
//
// The thread is still suspended when we reach here. Thread must be explicit started
// by creator! Furthermore, the thread must also explicitly be added to the Threads list
// by calling Threads:add. The reason why this is not done here, is because the thread
// object must be fully initialized (take a look at JVM_Start)
}
  • os::create_thread

最后可以定位到os::create_thread的实现hotspot/src/os/linux/vm/os_linux.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
assert(thread->osthread() == NULL, "caller responsible");

// Allocate the OSThread object
OSThread* osthread = new OSThread(NULL, NULL);
if (osthread == NULL) {
return false;
}

// set the correct thread state
osthread->set_thread_type(thr_type);

// Initial state is ALLOCATED but not INITIALIZED
osthread->set_state(ALLOCATED);

thread->set_osthread(osthread);

// init thread attributes
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

// stack size
if (os::Linux::supports_variable_stack_size()) {
// calculate stack size if it's not specified by caller
if (stack_size == 0) {
stack_size = os::Linux::default_stack_size(thr_type);

switch (thr_type) {
case os::java_thread:
// Java threads use ThreadStackSize which default value can be
// changed with the flag -Xss
assert (JavaThread::stack_size_at_create() > 0, "this should be set");
stack_size = JavaThread::stack_size_at_create();
break;
case os::compiler_thread:
if (CompilerThreadStackSize > 0) {
stack_size = (size_t)(CompilerThreadStackSize * K);
break;
} // else fall through:
// use VMThreadStackSize if CompilerThreadStackSize is not defined
case os::vm_thread:
case os::pgc_thread:
case os::cgc_thread:
case os::watcher_thread:
if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
break;
}
}

stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
pthread_attr_setstacksize(&attr, stack_size);
} else {
// let pthread_create() pick the default value.
}

// glibc guard page
pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

ThreadState state;

{
// Serialize thread creation if we are running with fixed stack LinuxThreads
bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
if (lock) {
os::Linux::createThread_lock()->lock_without_safepoint_check();
}

pthread_t tid;
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

pthread_attr_destroy(&attr);

if (ret != 0) {
if (PrintMiscellaneous && (Verbose || WizardMode)) {
perror("pthread_create()");
}
// Need to clean up stuff we've allocated so far
thread->set_osthread(NULL);
delete osthread;
if (lock) os::Linux::createThread_lock()->unlock();
return false;
}

// Store pthread info into the OSThread
osthread->set_pthread_id(tid);

// Wait until child thread is either initialized or aborted
{
Monitor* sync_with_child = osthread->startThread_lock();
MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
while ((state = osthread->get_state()) == ALLOCATED) {
sync_with_child->wait(Mutex::_no_safepoint_check_flag);
}
}

if (lock) {
os::Linux::createThread_lock()->unlock();
}
}

// Aborted due to thread limit being reached
if (state == ZOMBIE) {
thread->set_osthread(NULL);
delete osthread;
return false;
}

// The thread is returned suspended (in state INITIALIZED),
// and is started higher up in the call chain
assert(state == INITIALIZED, "race condition");
return true;
}

注意这行代码:

1
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

可以看到, Linux下, JVM Thread的实现是基于pthread_create实现的, 因此我们可以确定Java中Thread与操作系统中Thread之间是1:1的关系。

状态说明

Java JVM线程可以处于以下状态中:

  • NEW: 至今尚未启动的线程处于这种状态;
  • RUNNABLE: 正在Java虚拟机中执行的线程处于这种状态;
  • BLOCKED: 受阻塞并等待另一个线程来执行某一特定操作的线程处于这种状态;
  • WAITING: 无限期等待另一个线程来执行某一特定操作的线程处于这种状态;
  • TIMED_WAITING: 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态;
  • TERMINATED: 已退出的线程处于这种状态;

状态转换

状态转换说明

  1. 新建(new):新创建了一个线程对象。

  2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。

  4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  1. 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package concurrent.thread;

import java.util.concurrent.locks.ReentrantLock;

public class ThreadFun {

private static final Object l = new Object();
private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {

Thread t = new Thread(() -> {
System.out.println("Thread State When Running: " + Thread.currentThread().getState());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized(l) {
System.out.println("Thread State After Get Lock on l: " + Thread.currentThread().getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

});

System.out.println("New Thread State: " + t.getState());

t.start();

Thread.sleep(500);
System.out.println("Thread State When Sleep: " + t.getState());

synchronized (l) {
Thread.sleep(3000);
System.out.println("Thread State When Blocked: " + t.getState());
}

t.join();
System.out.println("Thread State After Run: " + t.getState());

}
}

示例输出:

1
2
3
4
5
6
New Thread State: NEW
Thread State When Running: RUNNABLE
Thread State When Sleep: TIMED_WAITING
Thread State When Blocked: BLOCKED
Thread State After Get Lock on l: RUNNABLE
Thread State After Run: TERMINATED

参考引用