线程管理

参见: http://harmony.apache.org/subcomponents/drlvm/TM.html

1. 修订历史

2. 关于本文档

3. 概览

4. 体系结构

5. 使用场景

6. 参考文献

1. 修订历史

版本

版本信息

日期

初版

Nadya Morozova, Andrey Chernyshev: 创建文档.

2006年6月5日

2. 关于本文档

2.1. 目的

本文介绍了线程管理器组件,该组件是作为动态运行时层(Dynamic Runtime Layer, DRL)计划的一部分递交的。本文主要关注DRL虚拟机中线程管理器任务的当前实现细节,以及线程管理子系统的内部组织结构。

2.2. 面向的读者

本文面向的读者包括对线程技术的进一步工作感兴趣,并且愿意贡献其开发的广大的工程师群体。本文假设读者对于DRLVM体系结构基础、线程方法学和结构都相当熟悉。

2.3. 文档约定

本文使用对DRL文档包的统一约定。

2.4. 文档使用

本文可以用来学习所有当前版本的实现细节。本文从多个方面描述线程管理器的功能,包括内部数据结构、体系结构细节以及线程管理器关键的使用场景。本文有如下主要部分:

3. 概览

线程管理器(TM)是一个致力于为Java*虚拟机提供线程支持的库,它的目标是在操作系统提供的类POSIX线程模型和由J2SE规范建立的类Java*线程模型之间架设一座桥梁。

在当前的实现中,JVM线程子系统包括以下三层:

需要注意的是:线程管理器包含该子系统的native layer和Java* layer;而porting layer在线程管理器外部。

每一层都为下层提供的线程支持增加某些功能。就是说,porting layer为操作系统提供的线程支持添加了可移植性(portability);native layer为porting layer提供的线程支持添加了Java*特定的增强;而Java* layer将Java*线程和对象连接到native layer,如图 1所示。这些接口被组织在一个头文件集合中,在下面的对外接口一节中描述。

http://harmony.apache.org/subcomponents/drlvm/images/ThreadingSystem.gif

图 1. 线程子系统

3.1. 主要特点

提供的线程管理器有如下特点:

3.2. VM中的线程管理器

图 2 展示了线程管理器与以下的虚拟机组件之间的交互:

http://harmony.apache.org/subcomponents/drlvm/images/tm_in_vm.gif

图 2. VM体系结构中的线程管理器

3.3. 可移植性

线程管理器的代码大部分是平台无关的,依靠底层的porting layer适应特定的平台。当前的TM实现在Apache Porting Layer(APR)之上通过添加额外的扩展完成。平台相关的TM部分包括绑定到特定体系结构上的VM 帮助器包和部分绑定到OS API上的APR扩展包。

基于APR的移植使得线程管理器代码可以在任何APR可用的平台上进行编译。线程管理器的当前版本支持Linux*和Windows* IA-32平台。

4. 体系结构

下面几节描述了线程管理器与其它VM组件交互时开放的功能接口及其内部数据结构。

4.1. 对外接口

如概览所述,线程管理器对外提供native和Java*接口。这些接口表现为一组为外部请求提供特定功能的函数,在后面的节中描述。

4.1.1. Native接口

Native接口由Harmony hythread模块得到启发。这是一个低级的提供类Java*线程功能的层,例如对等待操作(waitparkjoinsleep等)的中断支持。该接口帮助建立了线程与垃圾收集器间正确的交互。它并不处理Java*对象。

Native接口包含下列函数集:

hythread.h


hythread集合的函数8],其作用包括:

hythread_ext.h


扩展hythread集合的一组函数,其作用包括:

4.1.2. Java* 接口

Java*接口将native layer提供的线程支持功能连接到Java*线程和对象。

Java*的接口函数把Java*对象作为参数,可以容易地用来实现内核类(kernel classes)、JNI或者JVMTI函数集合。Java*接口包括三部分:

jthread.h


支持java.lang.Objectjava.lang.Thread API的函数,其作用包括:

ti_thread.h


支持不同JVMTI函数和java.lang.management API的函数,其作用包括:

thread_helpers.h


提供存根(stub)的函数。这些存根用于优化因线程管理器和JIT紧密地集成而带来的性能问题。

4.2. 数据结构

线程管理器的数据结构一般是不暴露的,外部的VM组件只能通过透明指针访问这些数据结构。这些指针被定义在头文件hythread.h、hythread_ext.h、jthread.hti_thread.h中。数据结构本身在thread_private.h中描述。

4.3. 线程控制结构

线程管理器要求每个线程在调用线程支持函数前要先被注册(registered)。线程注册被称为系附一个线程(attaching a thread),这可用如下函数实现:

依赖于系附函数(attaching function),线程管理器操作两种类型的线程:

每个线程类型有相关的结构,这个结构保存了线程特定的数据,在后面有所讲述。 其它VM组件使用操作那些结构的不透明句柄进行工作,而不能获得它们的内容信息。以这种方式与线程工作的时候,组件调用系附函数中的一个,获得该线程的线程控制结构的不透明句柄,然后就可以使用这个句柄对这个线城进行任何操作了。

4.3.1. Native 线程结构

当对线程管理器的native layer注册的时候,每个线程获得一个控制结构,这个结构包含所有执行线程操作所需的线程特定数据,例如:状态、属性、对特定OS线程结构的引用以及同步辅助等。这个控制结构随后为各种线程支持操作所使用。

下图展示了一个被HyThread类型描述的native系附线程的线程控制结构:

http://harmony.apache.org/subcomponents/drlvm/images/NativeUnattachedThread.gif

图 3. Native线程

下表列出了组成native线程的线程控制结构的数据域:

数据域

描述

挂起(Suspension)

IDATA

suspend_request

导致本线程挂起请求的数量。

int16

suspend_disable_count

指示线程可以被安全挂起的标志。

hylatch_t

safe_region_event

当一个线程进入安全区时用来通知相关线程的事件。

hysem_t

resume_event

用来通知一个挂起线程需要唤醒的事件。

hythread_event_callback_proc

safepoint_callback

当恢复一个线程时在安全点上执行的函数。

基本操作域(Basic manipulation fields)

hythread_group_t

group

本线程所在的组等同于该组的线程列表的表头地址。线程组被用来快速地在线程列表上迭代。例如:Java*线程、GC私有线程。

hythread_t

next

指向线程组中下一个线程。

hythread_t

prev

指向线程组中上一个线程。

apr_thread_t *

os_handle

OS线程的句柄。

void *

private_data

任何本线程相关数据的占位符。Java* layer使用该域存储与Java有关的上下文信息。

Boolean

exit_request

指示是否已经收到一个退出请求的标志。

IDATA

exit_value

本线程的退出值。

同步支持(Synchronization support )

hysem_t

park_event

暂停(Parking)线程事件。

hysem_t

sleep_event

Sleeping线程事件。

hylatch_t

join_event

为调用join的线程保留的事件。

hycond_t

current_condition

当前这个线程等待的条件变量。用作中断。

状态(State)

IDATA

state

线程状态。持有JVMTI规范中定义的线程状态标志加上附加标志。

属性(Attributes)

char *

name

线程名,用作调试。

IDATA

priority

为调度器提供线程优先级信息。

IDATA

daemon

检查该线程是否为守护进程。

其它(Others)

IDATA

thread_id

该线程的ID。线程的最大数目由锁字记录(lock word record)的大小决定。欲了解锁字记录结构的细节,请参见监视器结构一节。

apr_pool_t *

pool

内存池,线程被分配在其中。

apr_threadattr_t *

apr_attrs

APR线程属性。

hythread_entrypoint_t

start_proc

描述了会被执行的线程体的过程。

void *

start_proc_args

被传递到线程体的参数。

void *

thread_local_storage

代表线程本地存储的数组。

void *

big_local_storage

对标准本地存储槽的扩展。

线程控制结构的细节参见源代码包中的thread_private.h头文件。

4.3.2. Java* 线程结构

Java*线程的线程控制结构由JVMTIThread类型定义。它持有特定于那个线程的大部分JVMTI信息,如图 4所示。

http://harmony.apache.org/subcomponents/drlvm/images/JavaAttached.gif

图 4. Java* 系附的线程

下表列出了组成Java*线程的线程控制结构的数据域:

数据域

描述

JNIEnv *

jenv

与该Java*线程关联的JNI环境变量。

jthread

thread_object

hythread_t关联的jthread对象。

hycond_t

monitor_condition

用来wait/notify Java*监视器的条件变量。

jthrowable

stop_exception

被一个停止线程抛出的异常。

jlong

blocked_time

该线程在一个监视器上阻塞的时间,单位纳秒。

jlong

waited_time

该线程等待一个监视器的时间,单位纳秒。

JVMTILocalStorage

jvmti_local_storage

JVMTI本地存储。

jobject

contended_monitor

阻塞本线程的监视器。

jobject

wait_monitor

本线程正在等待的监视器。

jobject *

owned_monitors

本线程拥有的监视器。

int

owned_monitors_nmb

本线程拥有的监视器数量。

apr_pool_t *

pool

本结构体的APR池。

jobject

thread_ref

导致本线程挂起请求的数量。

IDATA

suspend_request

java.lang.Thread实例的弱引用。

线程控制结构的细节参见源代码包中的thread_private.h头文件。

与系附到java.lang.Thread对象上的线程关联的数据结构以及独立(未系附)的线程分别在图 3和图 4中展示。

4.4. 线程组

线程管理器使得多个线程组可以共存,例如,对Java*程序不可见的Java*线程组和GC线程组。每个被线程管理器维护的线程属于一个特定线程组,如图 5所示。

http://harmony.apache.org/subcomponents/drlvm/images/thread_groups.gif

图 5. 线程组

线程管理器提供一套函数用来在特定线程组中迭代遍历线程列表。所有线程被组织在一个线程组数组中,一个特定的系统范围的锁被用来防止对线程组数组以及组中线程列表的并发更改。在创建线程,删除线程以及迭代遍历线程列表时需要首先在内部申请这个锁。

4.5. 同步器

线程管理器同步器是用于线程同步的功能模块。某些同步器有与其自身关联的内部数据结构,其它同步器只能向APR提供的适当的同步器委托函数调用。当前在线程管理器中实现的同步器是基于两个基本原语:条件变量和互斥量,如图 6所示。

http://harmony.apache.org/subcomponents/drlvm/images/Synchronizer_mutex.gif

图 6. 线程管理器同步器的组件

图中的元素含义如下:

上面的层次是为APR代码复用进行的优化。线程管理器有可能有其它的实现,这些实现可以利用APR同步器的不同集合。

4.6. 监视器

线程管理器的当前版本在实现Java*监视器的时候使用一种特殊的方法处理空间消耗的一般公共问题。DRL线程管理器提供一种特殊类型的监视器——thin监视器;这种监视器持有的锁为空间消耗和单线程的使用提供了优化。

4.6.1. 膨胀技术

监视器膨胀用thin-fat锁技术6]实现,工作过程如下:

thin监视器的不同实现可以自由选择空间紧缩或其它优化技术(或并不优化)。但是,推荐的做法是当需要节省内存并且线程竞争不是很激烈时就应选用thin监视器。另外推荐的做法是,使用传统的互斥量和条件变量可以在高竞争的情况下获得更好的可伸缩性。线程管理器中的Java*监视器建立在thin监视器之上。这使得线程管理器可以在Java*对象中直接为thin监视器分配锁结构,而且因此Java*监视器的空间利用率大大提高。

4.6.2. 监视器结构

thin监视器是一个实现了锁压缩技术的同步器原语,它可以作为建立Java*监视器[6]的基础。换句话说,thin监视器驻留在TM子系统的native layer,不在Java*对象存放任何数据。Java*监视器与Java*对象是紧耦合的,驻留在TM子系统更高的Java* layer。

同步器的核心内容是锁字(lock word)。锁字持有thin锁值或者对fat锁的引用,这依赖于竞争情况。

在没有竞争的情况下,锁的类型是0,锁字有如下结构:

http://harmony.apache.org/subcomponents/drlvm/images/uninflated_lockword.gif

图 7. 锁字结构:Contention Bit为0

在有竞争的情况下,contention bit被设置为1,一个thin压缩的锁变成了一个fat膨胀锁,它有如下布局:

http://harmony.apache.org/subcomponents/drlvm/images/inflated_lockword.gif

图 8. 锁字结构:Contention Bit为1

线程管理器用一个张全局锁表实现(译注:fat)锁ID和相关fat监视器之间的映射,如下:

http://harmony.apache.org/subcomponents/drlvm/images/inflated_thin_monitor.gif

图 9. Thin和Fat监视器的关系

4.6.3. 获取一个监视器

借助hythread_thin_monitor_try_enter()函数可以获取一个监视器,其过程如下图:

http://harmony.apache.org/subcomponents/drlvm/images/Lock_reservation.gif

图 10. 申请一个Thin锁

首先,线程通过reservation bit检查所需的锁是否已被自己拥有。如果是,线程将recursion count加1,函数退出。这种做法使得单线程程序拥有最快速的monitor enter操作路径。这条快速路径只包含若干汇编指令,而没有高开销的原子比较-交换(compare-and-swap, CAS)操作。

如果锁还没有被保留,检查它的占用情况。如果锁是未被占用的(lock free),将它设置为“已保留”同时以一条CAS操作获得之。如果锁之后变成被占用的(lock busy),系统检查锁是否为fat锁。

锁表持有fat锁ID和实际监视器之间的映射关系。Fat监视器被从锁表中提取出来并被获取。如果锁不是fat并且被另一个线程保留,那么申请锁的线程挂起锁持有者线程,去除保留状态,然后再启动锁持有者线程。之后,再重新尝试获取锁。

5. 使用场景

这一节包括各种线程操作的场景。

5.1. Java* 线程生命期

Java*线程的创建过程包括如下主要步骤:

  1. 在创建了一个线程之后,Thread()构造函数创建一个新的native线程并初始化线程控制结构HyThreadJVMTIThread

  2. 用户应用程序然后调用VM core组件中内核类的java.lang.Thread.start()方法。

  3. java.lang.Thread.start()方法通过java.lang.VMThreadManager.start()函数向线程管理器中的Java* layer的jthread_create()函数委托一个调用。

  4. jthread_create()函数调用jthread_create_with_function()

  5. jthread_create_with_function()函数调用hythread_create()函数,提供wrapper_proc作为一个新线程体的过程。

  6. hythread_create()函数向APR porting layer的apr_thread_create()函数委托一个调用,它本身并不真正fork并创建一个新线程。在这一步,提供{{{wrapper_proc参数作为线程体。

  7. 新创建的线程开始执行thread_start_proc(),这个函数再转而执行wrapper_proc()函数。

  8. 函数wrapper_proc()执行新线程向其它组件的注册请求。方法是通过向VM core发送vm_attach事件,并调用jvmti_send_thread_start_end_event()函数,它会发送JVMTI_EVENT_THREAD_START事件。

  9. wrapper_proc()函数调用java.lang.Thead.run()方法,这个方法即是用户定义的新Java*线程的线程体。

  10. Thread.run()结束之后,线程向其它组件注销。方法是调用jvmti_send_thread_start_end_event()函数会,它会先发送JVMTI_EVENT_THREAD_END事件,然后是vm_detach事件。

下图展示了线程创建和完成的详细过程:

http://harmony.apache.org/subcomponents/drlvm/images/ThreadCreation.gif

图 11. Java*线程的生命周期

5.2. 线程挂起

native layer为APR线程支持添加的一个主要特征是安全挂起(safe suspension)。该机制保证了挂起的线程在活引用枚举期间可以被垃圾收集器安全地探测到。如果线程持有一些系统关键锁,如与本地堆内存关联的锁,安全挂起可以使得这些线程在枚举期间仍然运行。否则,一旦VM的其它部分需要同样的系统锁,使用系统调用或者“hard”调用来挂起线程可能导致死锁。

安全挂起的算法描述了两个线程间的通信协议,例如,有两个线程T1和T2,其中T1安全挂起线程T2。T1调用hythread_suspend(T2)函数来挂起线程T2。该过程步骤如下:

  1. hythread_suspend(T2)函数为线程T2增加指示请求挂起的标志。依赖于T2当前的状态,hythread_suspend(T2)函数执行下面机制中的一个:

    1. 如果线程T2当前运行在安全代码区,hythread_suspend(T2)调用立即返回,如图 12。

    2. 如果线程T2当前运行在非安全代码区,hythread_suspend()在T2到达安全区开始点或者安全点之前都会阻塞。

  2. 线程T2运行到安全区结束点,然后阻塞直到T1调用hythread_resume(T2)恢复它。

线程T2经历如下:

  1. 线程T2周期性地调用hythread_safe_point()函数指定安全挂起点。如果之前有对T2的挂起请求,该方法通知T1然后等待直到T1调用hythread_resume(T2)恢复T2。

  2. 当T2进入安全区,它调用hythread_suspend_ensable()方法,该方法减少suspend_disable_count状态标志。如果之前有对T2的挂起请求,T1会得到T2已经到达安全区的通知。

  3. 当T2离开安全区,它调用hythread_suspend_disable()函数,该函数增加suspend_disable_count状态标志。

一个典型的安全挂起场景的例子发生在垃圾收集器挂起一个Java*线程以便进行活引用枚举的时候。图 12 展示了GC使用线程管理器挂起运行在安全区的Java*线程的情况。

http://harmony.apache.org/subcomponents/drlvm/images/safeRegion.gif

图 12. 挂起:安全区

为了更好地理解安全线程挂起算法,考虑每个线程都有一个锁与之关联。当线程T2进入安全区时释放锁,离开安全区时获取锁。为挂起线程T2,需要获取与之关联的锁。恢复T2等同于释放与之关联的锁。安全挂起算法的一个直观的实现为每个线程保留一个单线程优化的锁(即:thin监视器)并且使用它来挂起和恢复那个线程。

另一个安全挂起情况是当一个GC线程碰到一个运行在非安全代码区的Java*线程,如图13所示。

http://harmony.apache.org/subcomponents/drlvm/images/safePoint.gif

图 13. 安全点

考虑hythread_safe_point()作为一个在和线程关联的监视器上执行的wait操作。在这种情况下,hythread_resume()等同于通知那个监视器。

5.3. Stop-the-world 线程挂起

当垃圾收集器需要为某个线程组中所有线程枚举活对象引用时,将发生stop-the-world线程挂起。图 14 展示了当只有一个GC线程但是有无数Java*线程在运行时的情况,此时GC需要挂起所有Java*线程。

http://harmony.apache.org/subcomponents/drlvm/images/SuspendAll.gif

图 14. 挂起一个线程组

首先,垃圾收集器调用线程管理器接口函数hythread_suspend_all()以便挂起运行在给定线程组中的每一线程(在本示例中,所有Java*线程)。线程管理器随后返回用于遍历被挂起线程列表的迭代器。GC使用该迭代器分析每一Java*线程与活引用的关系,然后进行垃圾回收。完成后,GC通知线程管理器恢复所有线程。

5.4. 线程锁定

涉及线程管理器的锁定可以通过互斥量或者thin监视器的方法实现。互斥量适合用在高度竞争的情况下,而thin监视器对空间进行了更好的优化。本节描述VM core试图锁定来自多个线程T1和T2的资源。锁定和解锁的主要过程如图 15所示。

http://harmony.apache.org/subcomponents/drlvm/images/locking.gif

图 15. 用互斥量锁定

开始,互斥量未被占用,就是说,标签锁被设置为0。线程T1调用hymutex_lock()函数,该函数通知线程管理器标记互斥量表示已经被T1锁定了。

T2也可以在随后调用hymutex_lock(),如果刚好所需的锁已经被占用,T2被放置在内部与互斥量关联的等待队列中,并阻塞直到T1对该互斥量解锁。T1调用hymutex_unlock()释放互斥量,这会使得互斥量从等待队列中提取T2,将锁的所有权授予T2,然后通知T2可以醒来了。

5.5. 监视器的进入和退出

锁定Java*监视器意味着在线程管理器和VM core之间的交互,因为线程管理器需要请求Java*对象中的内存地址,其中保存锁数据。锁定Java*监视器的过程如图 16所示。

如果Java*代码中有同步片段,会有以下步骤:

  1. JIT生成的代码调用线程管理器提供的hythread_monitor_enter()帮助器函数。帮助器函数提供一个代码块(存根),该存根可被JIT直接内联到所生成的汇编码中。

  2. hythread_monitor_enter()帮助器调用VM core组件的vm_object_get_lockword_addr()函数来查找Java*对象中锁字的物理地址。

  3. 帮助器调用thin_monitor_try_lock()函数以便获取对象关联的锁。

  4. 一旦锁被获取,则帮助器返回。这个获取Java*监视器的快速路径。在这个场景下,帮助器不需要在Java*和native帧之间切换,thin_monitor_try_enter()会被直接调用,不涉及任何Java*对象。否则,帮助器代码通过在Java*和native代码之间切换而进入一个慢速路径(参见下图中压入一个M2nFrame并创建本地句柄的动作)。

  5. 帮助器调用jthread_monitor_enter()函数,该函数就像在JNI代码一样与Java*对象协同工作。

http://harmony.apache.org/subcomponents/drlvm/images/Monitors.gif

图 16. 锁定Java*监视器

6. 参考文献

[1] J2SE 1.5.0 specification, http://java.sun.com/j2se/1.5.0/docs/api/

[2] JVM Tool Interface Specification, http://java.sun.com/j2se/1.5.0/docs/guide/jvmti/jvmti.html

[3] Java* Native Interface Specification, http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html

[4] Apache Portable Runtime project, http://apr.apache.org/

[5] POSIX standard in threading, http://www.opengroup.org/onlinepubs/009695399/idx/threads.html

[6] David F. Bacon, Ravi Konuru, Chet Murthy, Mauricio Serrano, Thin locks: featherweight synchronization for Java, http://portal.acm.org/citation.cfm?id=277734

[7] Kiyokuni Kawachiya Akira Koseki Tamiya Onodera, Lock Reservation: Java Locks Can Mostly Do Without Atomic Operation, http://portal.acm.org/citation.cfm?id=582433

[8] HyThread documentation, http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/doc/vm_doc/html/group__Thread.html

* Other brands and names are the property of their respective owners.

线程管理 (last edited 2009-09-20 21:54:51 by localhost)