当前位置:文档之家› Java虚拟机多线程之研究

Java虚拟机多线程之研究

云南省应用基础研究基金资助项目 . 1997- 06- 28 收稿
532
云南大学学报 ( 自然科学版 )
第 19 卷
线程有各自的寄存器和堆栈, 同时共享同一进程地址空间内的动态数据堆、 静态数据区和 代码区. 与进程相比 , 采用多线程模式来实现多任务并发具有以下优点: ( 1) 在进程中, 各个进程拥有自己独立的运行环境 , 独占自己的系统空间 , 进程之间的 耦合度差, 并发性粒度粗糙, 且进程上下文( cont ex t) 需在两个处理机之间切换, 系统资源开 销大. 而多个线程位于同一地址空间内, 共享该地址空间的内存区, 线程间的耦合度较好, 且在同一处理机内的线程切换速度较快 . ( 2) 进程间的通信需专门的系统机制( 如消息、 邮箱、 管道等) 来完成 , 调度复杂 . 而由于 线程共享同一地址空间, 线程间共享的实现则很简单. ( 3) 进程受系统的限制很大 , 如 : 用户能拥有的进程数目通常受操作系统的限制, 多进 程并发方式往往用于主机 ( 多用户 ) 系统 . 而多线程模式则不存在这些问题 . 由于多线程的上述优点, 多线程技术得到了广泛的应用 . 近年来的许多并发程序设计 语言都采用了并发多线程技术的设计思想 ( 如 Ada, cocurrent C 等 ) , 一些操作系统 , 如 Sun OS5. 3( solaris) , Window sNT , Window s95 等都为多线程提供了内核级的支持并在其 API 中 提供了有关线程的用户程序接口. IEEE 推出了多线程设计的 IEEE POSIX1003. 4a 标准 . 多线程程序设计中, 单个程序包含着多个并发执行的线程 . 在多线程并发执行时 , 线程 间的关系有时不可预先确定, 而依赖于运行时的条件( dependence on t im ing) , 多线程并发同 样具有不确定性 ( undet ermined) . 因此 , 多线程设计的一个关键问题就是解决多线程互斥访 问临界资源的同步问题. 多线程的同步可以采用多进程的同步机制 ( 如互斥锁、 信号量、 管 程等 ) . IEEE P OSIX1003. 4a 定义了线程间的两种 同步对象: 互斥锁 ( mutex) 和 条件变量 ( condit ion variable) , 互斥锁用于线程加锁时间不太长的场合 , 条件变量类似于信号量机制, 可用于加锁时间较长的场合. 任何一种同步机制 , 实际上都需要对临界资源进行加锁访问. 我们可以把加锁的同步层次划分为以下三种 ( DCE 标准 ) : ( 1) 粗粒度 ( coarse grain) 同步 : 即几个共享资源或一个进程中的所有线程仅用一把锁, 任一时刻只有一个线程能访问该锁 . ( 2) 细粒度 ( fine grain) 同步: 每个共享资源一把锁 . 在这种方式下 , 若一个数据结构或数 据对象有 n 个数据变量 , 则需 n 把锁 . ( 3) 中粒度 ( intermedia grain) 同步: 相关的几个共享资源使用同一把锁, 如对一个对象 用一把锁 . 2 Java 语言的多线程同步机制 Java 内在地 ( built in) 支持多线程设计. Java 采用的多线程同步机制的基本原理是 C. A. R. Hoare 的临界区保护和管程机制 . 管程 ( monit or) 是由若干公共变量及其说明和所有访 问这些变量的过程组成的一段程序区. 这些过程可由多个进程互斥地调用, 在任一时刻 , 最 多只允许一个调用者能真正地进入管程, 而其它调用者必须等待 . 对象的封装与管程十分 相似, 因此 , 管程可以很容易地用对象来实现 . Java 是面向对象的程序语言 , 在 Java 中 , 线程 已不再是一个过程, 而是一个对数据和方法进行了封装的对象. 当定义了一个线程( T hread) 类的对象时 , 也就定义了一个线程. 同时, 对象封装的数据和方法在被多个线程调用时, 也
摘要 关键词 分类号
介绍多线程的有关概念及 Java 语言级的多线程机制, 并着重探讨 Java 线程, 并发多线程, Java 虚拟机 T P312
虚拟机低层的多线程机制 .
1
线程的概念及多线程同步
线程的概念起源于进程. 传统的并发多任务的实现方法是在操作系统级上运行多个进 程. _进程是操作系统调度和分配资源的基本单位 , 一个进程拥有自已的独立的运行环境 ( 寄 存器和地址空间等) . 以多线程的方法来实现多任务并发是近年来受到日益广泛关注的一 种模式 . 线程( t hread) , 又称线索 , 是比进程更小的系统调度的逻辑单位 , 一个进程可由一个 或多个线程组成 , 一个单线程的程序就是一个进程. 每个线程拥有自己的堆栈、 寄存器等资 源, 同时共享所属进程的资源 . 在并发程序设计语言中 , 线程指程序中的单一顺序控制流, 多线程( mult ithread) 即一个程序中包含多个顺序控制流, 它们之间可以并发执行 . 传统的多 进程并发的多道程序 , 就其单个程序而言, 仅是一个线程. 为了便于比较, 我们在图 1 中给出了进程 ( 单线程 ) 与多线程的程序设计模式的比较, 在图 2 中给出了 Java 虚拟机的多线程模式结构 .
3
Java 虚拟机
Java 虚拟机 ( JVM ∀ Java Virt ual Machine) 是一台抽象的计算机, 是一个执行 Java 字节
码( by tecode) 的理想硬件结构的软件仿真. 一个 Java 源程序经 Java 编译器编译后变成独立 于平台的类格式文件 , 再由 Java 虚拟机运行 . Java 虚拟机仅规定了 Java 类文件格式字节码 形式的虚拟机指令集及部分操作规范 , 而具体的实现, 如运行数据的内存布局、 垃圾收集
图1
进程与多线程程序设计模式比较
F ig 1 . T he comparison of pro gram design model betw een process and multithread
从图 1 中我们可以看出: 右图中的 n 个线程位于同一地址空间 ( address space) 内 , 每个
534
云南大学学报 ( 自然科学版 )
第 19 卷ຫໍສະໝຸດ ( garbage collect ion) 算法、 字节码优化等 , 则可根据不同的硬件平台加以优化. 用户可以用软 件采用解释执行字节码方式或把 Java 虚拟机指令编译成实际的机器指令执行方式来实现 Java 虚拟机 , 也可以通过硬件方式在微代码级或直接在 Java 芯片上实现 Java 虚拟机. Java 虚拟机构成了 Java 跨平台的关键技术 , 与一般的机器相比 , Java 虚拟机具有以下 特点 : ( 1) Java 虚拟机仅识别类文件格式的字节码, 任何以以表示成符合虚拟机规范的类文 件的语言都可以在虚拟机上使用. ( 2) Java 虚拟机直接支持独立于平台的 Java 数据类型, 且按一个抽象的 word! 字来定 义运行数据区. ( 3) Java 程序的字节码表示中, 任何对变量和方法的引用都是符号引用 , 在运行时才转 化成数值引用, 确定其实际的存储布局, 且虚拟机对对象的内部结构不作规定, 仅通过 ref erence 类型的一个句柄来引用该对象. 这样, 同一个 Java 程序不作任何改动就可以在任何 一个支持虚拟机规范的平台上运行 . ( 4) 一条虚拟机指令直接指出了操作数的数据类型 ( 如 iadd 表示两个整型操作数相加 ) ( 5) Java 虚拟机采用面向堆栈的体系结构 , 仅使用程序计数器 ( PC) 、 栈顶指针 ( opt op) 、 运行环境指针( fram e) 、 局部变量指针( v ars) 等几个寄存器 . 这样, Jav a 虚拟机即使是在没有 通用寄存器或寄存器很少的机器上也能实现 .
ACC- SYNCHRONIZED= 其它非 0X0020 的值; / / 系统自动解锁隐式
系统自动隐式上锁和解锁部分 , 对用户来说是不可见的, 我们注意到 synchronized 语句 被编译成虚拟机 指令 monitorenter 和 monit orex it , 即使 是在 Java 虚 拟机这 一级, synchro nized 方法的上锁解锁过程对用户也是隐蔽的 . 在 monitor 机制下 , 若一个线程已得到了 monit or, 则其它线程要反复进行上锁测试 ( 如 例 1) , 这可能造成其它线程无休止的 盲等! . 对此需要扩充 monitor 的设计 , 使用显示的等 待( w ait ) 和唤醒( signal) 原语来解决此问题. Java 语言提供了三个标准 Objict 类方法: w ait ( ) , not ify( ) 和 not if yall( ) 来实现线程之间的控制转移. 避免反复检测锁的状态. Java 线程的 状态转换图如图 3 所示, 一个 Jav a 对象除了连着一把锁外, 还有一个 w ait 集 . 一个 w ait ( ) 方 法调用将挂起( suspend) 当前 运行的线程, 并将其放入相 应 synchronized 对象的 w ait 集中 去, 使其由运行态转入不可运行态, 直至有其它线程使用了 not if y( ) 方法或 notify all( ) 方法 来唤醒它. not if y( ) 方法相当于 signal, 调用 not ify( ) 方法将从相应 synchronized 对象的 w ait 集中唤醒一个线程, 使其变为可运行态. ( 该线程仍需等到当前线程退出了 monitor 之后才 能给共享对象上锁, 变为运行态 ) . 此外, wait ( ) 方法也可令当前线程暂时让出 monit or 一段 时间 , 由此可用来解决死锁问题.
第6期
沈勤祖等 : Java 虚拟机多 线程之研究
533
相当于管程 . Java 使用锁机制来解释 monitor 的行为 : Java 采用的同步层次为中粒度同步, 即对每个对象都使用一把锁, 锁的状态有两种 : 上锁( lock) 和解锁( unlock) . Java 语言使用修 饰符 synchronize 来表示需共 享临界资 源 ( 变量或 对象 ) 的方 法 ( 相当于 cobegin, coend 标 记) . 在 synchronize 方法执行之前, 必须对该方法所属的对象的锁进行上锁操作 ( 请求获得 该 monitor) . 如果得不到这把锁 ( 已被其它线程占用 ) , 则调用该 synchronize 方法的线程要 延迟直至对这把锁上了锁 ( 进入 monit or ) . 在用有 monitor 的线程执行期间, 其它线程不能 获得该 monitor, 在该线程调用的 synchronize 方法执行结束 ( 无论是正常还是异常结束) 之 后, 该线程自动释放 monit or. 上述对一个对象锁的上锁和解锁操作不是由线程 , 而是由 syn chronize 方法通过检测和设置常数池中的 ACC- SYNCHRONIZE 标志隐式自动地完成的. 其执行过程可用一个例子描述如下 : 例 1 synchronized 方法的执行 public sy nchronized vo id ex ample( ) { do{ } w hile ( ACC- SYNCHRONIZED= 0X0020) ; ACC- SYNCHRON IZED= 0X0020; 方法体 / / 方法内部语句, 是用户可见的部分 } / / 系统自动隐式完成 / / 的加锁部分
相关主题