《深入理解Java虚拟机》(三)--Java内存模型与线程(3)

Java与线程

并发并不一定要依赖多线程(比如PHP中很常见的多进程并发),但是Java里面谈论到并发,大多与线程脱不开关系。

1/1 线程的实现

主流操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例就代表了一个线程,Thread类与大部分API有明显的差异,它的所有关键方法都是声明Native的,一个Native方法意味着这个方法是使用非Java语言来实现的。线程实现的主要方式有三种:使用内核线程实现,使用用户线程实现和使用用户线程加轻量级进程混合实现。

  • 1.使用内核线程实现:内核线程就是直接由操作系统内核(Kernel)支持的线程,这种线程由内核来完成线程切换,内核通过调度器(Thread Scheduler)对线程进行调度,程序一般不会直接取使用内核线程,而是取使用内核线程的一种高级接口-----轻量级进程(LWP),轻量级进程就是我们通常意义上所说的线程,这种轻量级进程与内核之间1:1的关系成为1对1线程模型,如下图所示

    12350543-65e43d5e1355b9d1.png
    轻量级进程与内核线程之间1:1的关系

    优点:由于内核程序的支持,每个轻量级进程都成为了一个独立的调度单元,即使有一个轻量级进程发生了阻塞,也不会影响整个进程继续工作。
    缺点:由于是基于内核线程实现的,所以各种线程操作(如创建,析构和同步)都需要进行系统调用,调用的代价相对较高,需要在用户状态和内核状态来回切换,而且每个轻量级线程都需要内核进程的支持,因此一个系统支持轻量进程的个数是有限的。

  • 2.使用用户线程实现
    用户线程是指完全建立在用户空间的线程库上,系统内核不能感知线程存在。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。这种进程与用户线程之间1:N的关系称为1对多线程模型,如下图所示。

    12350543-f8d5c3cfdfbc6930.png
    进程与用户线程之间1:N的关系

    优点:这种线程不需要切换到内核态,因此操作可以是非常快速而且低消耗的,也可以支持更大的线程数量,部分高性能数据库的多线程就是由用户线程实现。
    缺点:由于没有系统内核的支援,所有线程操作都是由用户程序自己处理,处理起来过于复杂,Java已经放弃使用它。

  • 3.使用用户线程加轻量级进程混合实现
    除了上述两种实现外,还有一种将内核线程与用户线程一起使用的实现方式。这种混合方式下,既存在用户线程也存在轻量级进程,用户线程与轻量级线程的数量比是不确定的,即为N:M的关系,如下图所示,这种就是多对多线程模式(让我想起了Mysql数据库的关系模式....)。

    12350543-fde3f3c4739dc691.png
    用户线程与轻量级线程之间N:M的关系

    优点:用户线程完全建立在用户空间中,因此用户线程的创建、析构、切换等操作依然廉价,并且可以支持大规模的用户并发,书中并没有详细写出它的缺点0.0。

java线程的实现

在JDK1.2之后,线程模式为基于操作系统原生的线程模型来实现,线程模型只对并发规模和操作成本产生影响,对Java程序的编码和运行来说没有影响。Windows和Linux提供的线程模型为1对1,Solaris(Unix系列的操作系统)同时支持1对1和多对多,因此Solaris版的JDK中也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:UseBoundThreads来明确指定虚拟机使用哪种线程模式。

1/2 Java线程调度

线程调度是指系统为线程分配处理器使用权的过程。主要调度方法有两种,分别是协同式线程调度,和抢占式线程调度。

  • 协同式线程调度:线程的执行时间由线程本身来控制,执行完成后,主动通知系统切换到另外一个线程上。
    优点:实现简单,切换操作对线程自己来说是可知的,所以不会产生线程同步的问题。
    缺点:线程执行时间不可控制,可能由于程序编写问题一直不告知系统进行线程切换,那么程序就会一直阻塞在那里,相当不稳定。
  • 抢占式线程调度:每个线程将由系统来分配时间,线程的切换不由线程本身来控制。
    优点:不会因为一个线程而导致整个系统的阻塞,Java使用的线程调度方式就是抢占式调度(JDK后续版本可能会提供协成方式来进行多任务处理)。
    缺点:需要处理线程安全的问题。

1/3 状态转换

Java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有一种状态。这5种状态如下:

  • 新建(New):创建后尚未启动的线程处于这种状态。
  • 运行(Runnble):包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也可能在等待CPU为他分配执行时间。
  • 无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程唤醒,以下方法可以让线程进入无限期等待。
    • 没有设置Timeout参数的Object.wait()方法。
    • 没有设置Timeout参数的Thread.join()方法。
    • LockSuppot.park()方法。
  • 限期等待(Timed Waiting):处于这种状态的线程也不会被CPU分配执行时间,不过无需等待被其他线程唤醒,在一定时间后他们会由系统自动唤醒,以下方法会让线程进入限期等待状态。
    • Thread.sleep()方法。
    • 设置了Timeout参数的Object.wait()方法。
    • 设置了Timeout参数的Thread.join()方法。
    • LockSuppot.parkNanos()方法。
    • LockSuppot.parkUntil()方法。
  • 阻塞(Blocked):线程被阻塞了,阻塞状态等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

上述5种状态在遇到特定事件的时候可以相互转换,它们的转换关系如下图所示。


12350543-b4fe770d46012af1.png
线程状态转换关系
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章