线程简介

线程是比进程更轻量级的调度执行单位,线程的引入分离了进程的资源分配和执行调度两个功能。各个线程直接可以共享资源,又可作为最小的调度单元。目前线程是Java进行处理器调度的最基本单位,但在将来可能由纤程Fiber作为处理器调度的最基本单位。

并发不一定要通过多线程,例如PHP通过多进程实现并发,只不过在Java中的并发都与线程有千丝万缕的联系。

线程的实现方式

1、内核线程(1:1)

内核级线程KLT就是由操作系统内核支持的线程。

  1. 由内核完成线程切换
  2. 通过调度器(Scheduler)对线程进行调度
  3. 负责将各个任务映射到各个处理器上
  • 程序一般不会直接使用内核线程,而是使用内核线程的高级接口——轻量级进程LWP

  • 基于内核线程实现,线程的创建、析构、同步等操作都需要进行系统调用,开销较大
  • 每个轻量级进程LWP都需要一个内核线程支持,因此每个轻量级进程都需要消耗一定的内核资源,一个系统支持的轻量级进程数目是有限的。

2、用户线程(1:N)

狭义上的用户线程是指完全在用户空间的线程库上,系统内核不能感知到线程的存在及如何实现的。用户线程的建立、调度、同步、销毁等完全在用户态下执行,不需要内核的帮助,操作速度快且消耗低,能支持更大的线程数。

劣势同样也出在没有内核支持,使用用户线程实现的程序通常比较复杂,因为不仅要解决线程的创建、销毁、调度、切换,由于操作系统只把处理器资源分配给进程而不负责线程的调度因此还要考虑用户线程“阻塞如何处理”、“多处理器如何将线程映射到其他处理器上”。

3、混合实现(N:M)

即存在用户线程,也存在内核线程。操作系统支持的轻量级进程作为用户线程和内核线程之间的桥梁。这样就可以使用内核提供的线程调度及处理器映射功能,而且支持大规模用户级线程的并发,还降低了整个进程被阻塞的风险。

线程调度

Java线程如何实现不受Java虚拟机规范的制约,操作系统支持怎样的线程模型将会很大程度上影响Java虚拟机的线程,各个平台上很难达成一致。线程模型只对线程的并发规模和操作成本有影响,对Java程序的编译和运行过程来说是透明的。

HotSpot每一个Java线程都是直接映射到一个操作系统内核原生线程实现的,虚拟机基本上不干预线程调度。(当然有些系统如Solaris平台虚拟机可以通过调节虚拟机参数使用混合实现)

线程调度方式

协同式线程调度,线程的执行时间由线程本身控制。实现简单,线程本身执行完才会进行线程调度,一般没有同步的问题。缺点是线程的运行时间不可控,可能会出现由于某一个线程长时间运行不让出处理器导致程序崩溃。

抢占式线程调度,每个线程由系统进行分配,运行时间操作系统可控。

Java线程调度

Java采用抢占式线程调度,通过Thread类的Priority来进行设置进程优先级,但是由于最终还是要由操作系统决定优先级,java Thread类中设置的优先级仅是一个参考。而且各个系统提供的优先级概念不一,Solaris分级2^31种优先级,而Windows只有7种优先级,需要把Java中的10级优先级与系统优先级进行映射

Java线程状态

  • 新建(New):线程创建后尚未启动的状态
  • 运行(Runnable):包括操作系统中的执行态和就绪态,即线程可能正在运行,也可能在等待系统分配时间片
  • 无限期等待(Waiting):处于这种状态的线程不会被分配处理器时间,需要等待被其它线程显示唤醒
    • 没有设置TimeOut参数的Object:wait()方法
    • 没有设置TimeOut参数的Thread:join()方法
    • LockSupport:park()方法
  • 有限期等待(Timed Waiting):处于这种状态的线程不会被分配处理器时间,不过不需要被线程唤醒,而是等待一段时间会由系统自动唤醒
    • Thread:sleep()方法
    • 设置了TimeOut参数的Object:wait()方法
    • 设置了TimeOut参数的Thread:join()方法
    • LockSupport:parkNanos()方法
    • LockSupport:parkUntil()方法
  • 阻塞(Blocked):在等待获取一个排它锁的状态,在等待另外一个线程放弃这个锁,在进入同步区域时会进入这个状态
  • 结束(Terminated):已终止线程的线程状态