Java与线程

线程的实现

线程是CPU调度基本单位,是比进程更轻量级的调度执行单位,线程可以把进程的资源分配执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O),又可以独立调度。

主流操作系统都提供了线程实现,Java提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且还未结束的Thread类的实例代表一个线程。且Thread类所有关键方法都被声明为Native。在JavaNative方法意味着该方法没有使用或无法使用平台无关的手段实现,也可能为了执行效率而使用Native方法,通常最高效的手段是平台相关的手段。

实现线程主要有3种方式:使用内核线程实现使用用户线程实现使用用户线程加轻量级进程混合实现

使用内核实现

KTL内核线程是直接由操作系统内核直接支持的线程,由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可视为内核的一个分身

程序一般不会使用内核线程,而是去使用内核线程的一种高级接口轻量级进程LWP轻量级进程就是通常意义上所说的线程每个轻量级进程都由一个内核线程支持,这种关系称为一对一线程模型

轻量级进程与内核线程之间1:1的关系

每个轻量级进程都成为一个独立的调度单元,但轻量级进程具有局限性,由于是基于内核线程实现的,故各种线程操作都需要进行系统调用。而系统调用的代价相对较高,需要在用户态内核态来回切换。每个轻量级进程都需要一个内核线程支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的

使用用户线程实现

广义上讲一个线程只要不是内核线程,就可以认为是用户线程,因此轻量级进程也属于用户线程,但轻量级进程始终是建立在内核之上的,许多操作系统都要进行系统调用效率会受限

狭义上讲用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核帮助。若程序实现得当,这种线程不需要切换到内核态,因此操作可以非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程用户线程之间的1:N的关系称为一对多的线程模型

用户线程优势在于不需要系统内核支援劣势在于没有系统内核支援所有线程操作都需要使用用户程序自己处理。由于操作系统只把处理器资源分配到进程,诸如阻塞处理多处理器系统将线程映射到其他处理器上这类为题解决起来异常困难,甚至不可能完成。现在使用用户线程的程序越来越少了,Java曾经使用过用户线程但最终放弃了。

使用用户线程加轻量级进程混合实现

还可以将内核线程用户线程一起使用,在这种混合实现下,既存在用户线程,也存在轻量级进程

用户线程与轻量级进程间的N:M的关系

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

Java线程的实现

JDK1.2之前是基于用户线程实现的,JDK1.2中线程模型替换为基于操作系统原生线程模型来实现。Sun JDKWindows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程中。

由于Solaris平台中操作系统的线程特性可同时支持一对一多对多的线程模型。因此Solaris版的JDK中对应提供了两个平台专有的虚拟机参数-XX:+UseLWPSynchronization-XX:UseBoundThreads来明确指定虚拟机使用哪种线程模型。

Java线程调度

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

使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完后,要主动通知系统切换到另一个线程上优点实现简单切换操作对线程自己是可知的没有线程同步问题缺点是线程执行时间不可控

使用抢占式调度的多线程系统,线程将由系统来分配执行时间,线程的切换不由线程本身来决定Thread.yield()可以让出执行时间,但线程没办法主动获取执行时间。优点线程执行时间可控,不会产生由一个线程导致整个进程阻塞。

虽然Java线程调度是由系统自动来完成,但可通过设置线程优先级来给系统建议给某些线程多分配或少分配一点执行时间。Java中一共设置了10个级别的线程优先级,两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行

线程优先级并不靠谱,因为Java线程是映射到系统原生线程上来实现的,最终线程调度还是取决于操作系统,虽然很多系统都提供线程优先级的概念,但并不一定能与Java线程优先级一一对应,且优先级可能被系统自动改变。

状态转换

Java定义了五种线程状态,在任意时间点,一个线程只能有且只有其中一种状态。

  • 新建(New:创建后未启动的线程处于该状态
  • 运行(Runable:包括了操作系统线程状态中的RunningReady,处于次状态的线程有可能正在执行,也可能在等待CPU为他分配执行时间。
  • 无限期等待(Waiting:该状态下线程不会被CPU分配执行时间,它们要等待被其他线程显式地唤醒。产生:
    • 没有设置Timeout参数的Object.wait()方法
    • 没有设置Timeout参数的Thread.join()方法
    • LockSupport.park()方法
  • 限期等待(Timed Waiting:不会被CPU分配执行时间,无需等待被其他线程显式唤醒,在一定时间之后会由系统自动唤醒。产生:
    • Thread.sleep()方法
    • 设置了Timeout参数的Object.wait()方法
    • 设置了Timeout参数的Thread.join()方法
    • LockSupport.parkNanos()方法
    • LockSupport.parkUntil()方法
  • 阻塞(Blocked:等待获取到一个排他锁,该事件将在另一个线程放弃这个锁时发生;
  • 结束(Terminated:线程已结束执行。

线程状态转换关系