一、线程概述
线程是比进程更轻量级的调度执行单位,各个线程可以共享进程的资源,包括内存地址、文件I/O等。
1.1 Java线程的实现
通过Thread的源代码,可以看见,Thread类的很多方法都是Native关键字的,这说明这些方法很多都是依赖平台去实现的。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads(POSIX线程)作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32,而且为各个语言开发的线程调用提供了WindowsAPI。
线程的实现主要有三种方式:使用内核线程、使用用户线程、使用用户线程加轻量级进程混合实现
1.1.1 内核线程
内核线程(KLT)就是由操作系统内核完成线程切换,内核通过操纵调度器对线程进行调度的线程。内核负责将线程的任务映射到各个CPU上
程序一般不直接使用内核线程,而是调用内核线程的高级接口:轻量级进程,LWP。轻量级进程就是我们通常意义上所讲的线程,每个LWP都由一个内核线程支持。这种1:1的关系成为一对一的线程模型。
基于内核线程的实现,缺点在于系统调用的代价相对较高,需要在用户态和内核态之间切换。而且每个轻量级进程都需要一个内核线程支持,需要消耗一定的内核资源。
目前就Sun JDK来说,它的linux版和Windows版都是采用了一对一的线程模型。
1.1.2、用户线程实现
用户线程的建立、同步和调度完全在用户态中完成,不需要内核参与。基于用户线程实现的劣势在于没有系统内核的支持,线程创建、调度和切换都需要用户程序自己处理,实现的程序一般都比较复杂。
1.1.3、用户线程加轻量级进程混合
用户线程还是建立在用户空间中,而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,可以使用内核提供的线程调度功能及处理器映射。这种模式中,用户线程和轻量级进程的数量比是不定的,所以称之为多对多的线程模式。
1.2、Java线程调度
线程调度是指系统为线程分配处理器使用权的过程。主要的调度方式有两种:协同式调度、抢占式调度
1.2.1、协同式调度
线程把自己的工作执行完后,要主动通知系统切换到另一个线程上。实现简单,但是线程时间不可控,遇到死循环线程会导致整个系统奔溃
1.2.2、抢占式调度
每个线程由系统分配时间,线程的切换由系统决定。好处是不会因为一个线程的问题导致系统奔溃。
1.3、状态转换
Java定义了6个线程状态。
新建(New):创建后未启动
运行(Running):正在执行,或者等到CPU为其分配执行时间
无限等待(Waiting):等待被其他线程显示地唤醒
限期等待(Timed Waiting):在一定的时间后会由系统唤醒
阻塞(Blocked):在等待着获取一个排他锁
结束(Terminated):已终止
二、Java内存模型
Java内存模型是为了让Java程序在各种平台下都能达到一致的内存访问效果。
2.1、主内存与工作内存
Java的主内存存储了所有变量,每条线程还有自己的工作内存,工作内存保存了被该线程所使用到的变量的主内存副本拷贝。
2.2、内存间交互操作
Java内存模型提供了8种原子操作来完成工作内存和主内存之间的交互协议。
lock:作用于主内存的变量,把一个变量标识为一条线程独占
unlock:作用于主内存变量,把锁定的变量释放出来
read:作用于主内存的变量,把变量从主内存中传输到工作内存中
load:作用于工作内存的变量,把read传输的变量放入到工作内存的副本中
use:作用于工作内存的变量,把工作内存的变量的值传递给执行引擎。
assign:作用于工作内存的变量,把执行引擎接收到的值赋给工作内存的变量。
store:作用于工作内存的变量,把工作内存的变量传输到主内存中
write:作用于主内存,把store的变量值写入到主内存中
一个变量从主内存到工作内存,需要执行read-load两个操作,如果从工作内存到主内存,需要store-write操作。这两个操作都需要顺序执行,但不保证是连续执行。
2.3、volatile型变量
volatile关键字有两个特性:可见性、禁止指令重排优化
2.3.1、可见性
当一个线程修改了volatile变量的值,此时其他线程都是立即可见的。因为所有线程读取volatile变量的值,都需要重新从主内存中刷新,然后再use。volatie变量的可见性并不代表所有基于volatile的运算在并发下是安全的。因为Java的运算并非原子操作。
volatile适用场景:让一个变量的修改被其他线程立即感知。
2.3.2、禁止指令重排优化
普通的变量只能保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值的操作的顺序与代码中的执行顺序一致。
用volatile修饰的变量赋值后,会存在一个内存屏障,,把修改同步到内存是,意味着之前素有的操作都已经执行完毕,形成了指令重排序无法越过内存屏障的效果。
2.4、先行发生原则
先行发生原则是指:Java内存模型中定义的两项操作直接的偏序关系,如果A操作先行发生于B操作,就是说发生B操作前,操作A的产生的影响能被B操作观察到。
Java内存模型存在的先行发生关系:
程序次序规则:一个线程内,控制流(分支、循环)是顺序的。
管程锁定规则:一个unlock先行发生于后面对同个锁的lock操作。
volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个操作的读操作
线程启动规则:Thread的start()先行发生于此线程的其他操作
线程终止规则:此线程的其他操作都先行发生于此线程的终止检测
线程终端规则:线程的interrupt方法先行发生于被终端线程的代码检测到中断事件的发生
对象终结规则:一个对象的初始化完成先行发生于它的finalize方法的开始
传递性:如果A先行发生于B且B先行发生于C,则A先行发生于C
借鉴:维基百科:https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B#cite_note-8
《深入理解Java虚拟机》