线程的实现
实现线程主要有三种方式:使用内核线程实现(1:1实现),使用用户线程实现(1:N实现),使用用户线程加轻量级进程混合实现(N:M实现)
内核线程实现
使用内核线程的实现方式也被称为1:1实现。内核线程(Kernel- Level Thread, KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就称为多线程内核(Multi-Threads Kernel)
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为1对1的线程模型
由于内核线程的支持,每个轻量级进程是一个独立的调度单元,即使其中某一个轻量级进程在系统调用中被阻塞了,也不会影响整个进程继续工作。
轻量级进程的局限性:
系统调用代价较高
消耗一定的内核资源(内核线程的栈空间)
因此支持轻量级进程的数量有限
用户线程实现
使用用户线程实现的方式被称为1:N实现。广义上讲,一个线程只要不是内核线程,都可以认为是用户线程(User Thread,UT)的一种。
用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
优势:
由于不需要切换到内核态,因此操作可以是非常快速且低消耗的
支持规模更大的线程数量
劣势:
所有线程操作都需要由用户程序自己处理
混合实现
将内核线程与用户线程一起使用的方式,被称为N:M实现。
优势:
用户线程的创建、切换、析构等操作廉价且支持大规模的用户线程开发
轻量级进程作为用户线程和内核线程的桥梁,降低了整个进程被完全阻塞的风险。
Java线程的实现
操作系统全权决定
Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式(Cooperative Threads-Scheduling)线程调度和抢占式(Preemptive Threads-Scheduling)线程调度。
协同式调度多线程系统,线程执行时间由线程本身控制,线程工作执行完,通知系统切换至另一线程。
优势:实现简单,一般没有线程同步问题
劣势:线程执行时间不可控,一个进程阻塞导致整个系统崩溃
抢占式调度多线程系统,系统负责为每个线程分配时间执行
优势:线程执行时间系统可控,不会导致一个线程使整个进程阻塞的问题
Java线程优先级与Windows线程优先级的对应关系
状态转换
Java语言线程的6种状态:
新建(New):创建后尚未启动的线程状态
运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是表示线程正在执行或者线程在等待操作系统为它分配执行时间
无限期等待(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):已终止线程的线程状态,线程已经结束执行