本篇 [并发与多线程系列] 的第一篇,对应Java知识体系脑图中的 并发与多线程 模块。
这一系列将对Java中并发与多线程的内容来展开。
Java与线程、线程的状态
Java与线程、线程的状态
与线程有关的一些基础知识,在博主之前的博客 Java基础(十三):多线程 有提及,不过有些基本概念本篇仍然会描述,各位可以直接从本篇开始阅读。
线程、并发的概念
进程和线程
Java之父对线程的定义是:
线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进程所创建的对象资源(内存资源)。java.lang.Thread对象负责统计和控制这种行为。
每个程序都至少拥有一个线程-即作为Java虚拟机(JVM)启动参数运行在主类main方法的线程。在Java虚拟机初始化过程中也可能启动其他的后台线程。这种线程的数目和种类因JVM的实现而异。然而所有用户级线程都是显式被构造并在主线程或者是其他用户线程中被启动。
进程
- 进程是资源分配的最小单位,运行一个程序就会产生一个进程。
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含 1 — n 个线程。
线程
- 线程是CPU调度的最小单位,进程至少包含1个线程,是程序的多个顺序的流动态执行。
- 同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
进程与线程的关系
- 进程是抢占处理机的调度单位,线程属于某个进程,共享其资源。
- 一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行。
并行与并发
并发
- 并发是指两个或多个事件在同一时间间隔发生。在操作系统中,是指一个时间段中有几个程序都处于已启动到运行完毕之间,且他们都处于同一个处理机上。
并发并不是真正的“同时进行”,而是CPU把一个时间段划分成几个时间片断(时间区间),然后在其间来回切换,CPU处理得飞快,感觉就像是同时进行。
并行
- 并行,即当系统有一个以上的CPU时,两个进程同时进行,互不抢占资源。
并发和并行的本质都是充分利用CPU资源,提高效率。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
Java与线程
线程是比进程更轻量的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度(线程就是CPU调度的基本单位)。
线程的实现
线程的实现主要有3种方式:使用内核线程实现、使用用户线程实现、使用用户线程加轻量进程混合实现。
使用内核线程实现
内核线程(Kernel-Level Thread,KLT)是直接由操作系统内核(Kernel)支持的线程,这种线程由内核完成切换,通过操作调度器(Scheduler)对线程进行调度,并将线程任务映射到各个处理器上。
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口 —— 轻量级进程(LWP),轻量级进程就是我们通常意义上所讲的线程,每个轻量级进程都有一个内核级线程支持,因此只有先支持内核线程,才能有轻量级进程。轻量级进程与内核线程之间的关系是一对一关系。
局限性:
- 由于是基于内核实现的,调用的代价比较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。
- 每个系统支持的轻量级进程的数量是有限的。
使用用户线程实现
广义上来说,只要不是内核线程就可以认为是用户线程,因此可以认为轻量级进程也属于用户线程。狭义上说是完全建立在用户空间的线程库上的并且内核系统不可感知的。
用户线程的简历、同步、销毁和调度完全在用户态中完成,不需要内核的帮助,进程与用户线程之间是1:N的关系。
使用用户线程的优势在于不需要系统内核的支持,劣势也在于没有系统内核的支持。没有系统内核的支持下,所有线程操作都需要用户程序自己处理,且把处理器分配到进程上非常困难,如“阻塞如何处理”等需要内核支持的问题。目前用户线程方式使用甚少。
使用用户线程加轻量进程混合实现
混合实现方式中,用户线程还是在用户态中自行创建,使用轻量级进程作为用户线程与内核之间的桥梁。用户线程和轻量级进程的数量比是不定的,所以为N:M关系。
Java中线程的实现
对于Sun公司的HotSpot JDK来说,它的Windows和Linux版本都是使用的一对一线程模型实现的,一条Java线程映射到一条轻量级线程之中。因为Windows和Linux操作系统提供的就是一对一线程模型。
对于Solaris平台中,由于操作系统可以同时支持一对一和多对多的线程模型,则可以通过对应参数进行设置。
线程的调度
- 协同式线程调度
线程执行时间由线程自身控制,实现简单,切换线程自己可知,所以基本没有线程同步问题。坏处是执行时间不可控,容易阻塞。
- 抢占式线程调度
每个线程由系统来分配执行时间。缺点是只能主动让出执行时间(Thread.yield();),没有办法获取执行时间。Java使用这种调度方式。
线程的状态
Java中线程的状态
在Java的Thread源码中,对线程状态有如下定义:
public enum State {
NEW,//新生状态
// 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
RUNNABLE,//运行状态,(包含Ready 和 Running 两个状态)
// 在jvm中运行的线程状态,线程也可能处于等待获取资源的就绪状态。可用isAlive()方法验证。</