1. 概述
众所周知,线程 - Thread 是比进程 - Progress 更轻量级的调度单位。简单来说,引入线程带来的好处是:
可以把一个进程 的资源分配和执行调度分开,各个线程 既可以共享进程 资源(内存地址、文件I/O等),又可以独立调度。
线程实现方式:
主流的操作系统都实现了线程 ,而编程语言一般会提供关于线程 的统一API操作。那么,编程语言如何去调用系统线程 呢?这方面主要有3种方式:
- 使用内核线程 - Kernel Thread。
一对一线程模型 ,这个最重要,下面详细讲。
- 使用用户线程 - User Thread。
一对N线程模型 ,用户程序完全模拟线程的行为,编码时操作的线程是用户程序模拟的线程,操作系统内核不能感知你做了创建、调度等等线程操作。许多编程语言最初使用过这种方式,但现在基本放弃了这种。
- 使用用户线程 + 轻量级进程混合实现。
N对M线程模型 ,也有很多语言使用混合实现。
2. Java线程模型
- JVM线程模型的选择:
首先,JVM规范没有限定JVM要使用哪种线程模型,具体的JVM采用什么线程模型与很多因素包括所在的OS有关;
其次,线程模型只对线程的并发规模和操作成本产生影响。
所以,线程模型对于我们程序员编码来说是透明的。
- SUN JDK中:
对于官方JDK来说,因为Windwos和Linux操作系统(主流)只提供一对一线程模型 ,所以SUN JDK采用了一对一线程模型 。其它JDK可以有不同实现(如Solaris JDK可以采用N对M模型)。
- 内核线程方式:
如上所述的三中方式中,SUN JDK采用的内核线程 。这种方式的线程调度流程如下:
调度流程:
- 首先,从OS方面来说:如果操作系统内核中包括了一个线程调度器(Scheduler) ,内核可以操作调度器对内核线程 进行调度,将线程任务映射到各个CPU上。那么,这样的内核就叫多线程内核 。
- 其次,从程序来说:应用程序不能直接使用内核线程,只能去使用内核线程的一种高级接口 - 轻量级进程(LWP) 。对于编程语言来说,它们操作的实际上是这个LWP,LWP就是此时就可以被叫线程 。
- 最后,轻量级进程 和线程 之间的映射关系是1:1的,所以,这种方式叫做一对一线程模型 。
3. Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要的调度方式有两种:
- 协同式线程调度(Cooperative Threads-Scheduling)
这种方式是原始方式,由一个线程执行完通知另一个线程。已经很少使用,很容易造成阻塞。
- 抢占式线程调度(Preemptive Threads-Scheduling)
主流方式,由系统来根据一系列复杂的规则为每个线程分配执行时间,线程的切换不是由线程自己做主(Java可以有Thread.yield()来让出执行时间,但是没有获取执行时间的方式)。
虽然抢占式调度是系统自动完成的,但是我们可以给出“建议”,即通过设置线程的优先 - priority 。
- Java语言有10个级别的线程优先级,其实在代码中就是1 - 10 十个int常量(默认5),通过
setPriority(int x)
来设置。 - 但是不要依赖优先级,因为它并不靠谱,最终结果仍取决于OS。仅举一例,比如Java有10种优先级,而Windows只提供了7种,那它们怎么可能一一对应呢?
所以,不要太依赖优先级。
请注意,抢占式调度总体来说是绝对由OS内核来决定的,程序只能使用“祈使语句”,即使是放弃使用CPU。
比如,
Thread.yield(int x);
中这么说:A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
4. 线程状态及状体转换
1. Java语言规范定义了如下6种线程状态:
- New - 新建
创建后,尚未启动的线程
- Runable - 可运行
- 正在JVM中执行的线程处于这种状态(注意不是在CPU中执行,见2)
- Runable对应了两种OS线程状态Running和Ready,也就说Runable状态的Java线程有可能正在执行,也有可能正在等待CPU为它分配执行时间
- Blocked - 阻塞的
其它线程占有的监视器的锁,此线程等待着进入同步区域的时候(等待获取锁),线程处于这种状态
- Waiting - 无限期等待
处于监视器所属对象的wait set中,等待着被其它(此对象监视器所有者)线程唤醒。调用如下方法会时当前线程进入Waiting状态:
- obj.wait()/obj.wait(0)方法
- threadObj.join()/threadObj.join(0)方法
- LockSupport.park()方法
- Timed Waiting - 期限等待
在一定时间内会被系统自动唤醒,当然也会被唤醒。调用如下方法会让线程进入期限等待状态:
- threadObj.sleep(n)方法
- threadObj.join(n)方法
- LockSupport.parkNanos(obj ,n)方法
- LockSupport.parkUntil(obj ,n)方法
- Terminated
已经终止的线程处于这种状态
2. 状态转换如下
需要注意的是,这些状态并不一定能通过线程的Thread.getState();
方法来获取。不如,一个Waiting中的进程它肯定做不出getState()
动作。
《Java线程和线程同步 - 线程(2)》
参考文献:
[ 1 ] 周志明.深入理解Java虚拟机[M].第2版.北京:机械工业出版社,2015.8.
[ 2 ] James Gosling,Bill Joy,Guy Steele,Gilad Bracha,Alex Buckley.The Java? Language Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.