(转)《深入理解java虚拟机》学习笔记10——并发编程(二)
Java的并发编程是依赖虚拟机内存模型的三个特性实现的:
(1).原子性(Atomicity):
原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的。
Java内存模型中通过read、load、assign、use、store和write这6个操作保证变量的原子性操作。
long和double这两个64位长度的数据类型java虚拟机并没有强制规定他们的read、load、store和write操作的原子性,即所谓的非原子性协定,但是目前的各种商业java虚拟机都把long和double数据类型的4中非原子性协定操作实现为原子性。所以java中基本数据类型的访问读写是原子性操作。
对于大范围的原子性保证需要通过lock和unlock操作以及synchronized同步块来保证。
(2).可见性(Visibility):
可见性是指当一个线程修改了共享变量的值,其他线程可以立即得知这个修改。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。
Java中通过volatile、final和synchronized这三个关键字保证可见性:
volatile:通过刷新变量值确保可见性。
synchronized:同步块通过变量lock锁定前必须清空工作内存中变量值,重新从主内存中读取变量值,unlock解锁前必须把变量值同步回主内存来确保可见性。
final:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this引用传递进去,那么在其他线程中就能看见final字段的值,无需同步就可以被其他线程正确访问。
(3).有序性(Ordering):
线程的有序性是指:在线程内部,所有的操作都是有序执行的,而在线程之间,因为工作内存和主内存同步的延迟,操作是乱序执行的。
Java通过volatile和synchronized关键字确保线程之间操作的有序性。
volatile禁止指令重排序优化实现有序性。
synchronized通过一个变量在同一时刻只允许一个线程对其进行lock锁定操作来确保有序性。
JDK线程的实现如下:
(1).Kernal thread:KLT,内核线程,运行在内核态,是直接有操作系统内核支持的线程,有操作系统内核完成内核线程切换,内核操作线程调度器Threadscheduler对内核线程进行调度,负责将内核线程任务映射到各个处理器上。
(2).Light weight process: LWP,轻量级用户进程,是编程中传统意义上的线程,每个轻量级进程都由一个内核线程支持。
(3).User thread:UT,用户线程,运行在用户态,完全由用户空间线程库实现,内核线程无法感知到用户线程的实现,用户线程的创建、同步、调度和销毁完全在用户态中完成,不需要内核态的支持。
JDK的线程是基于操作系统原生线程模型来实现的,因此JDK版本中线程模型取决于java虚拟机线程与操作系统线程的映射,在不同平台上是不同的。
线程调度有两种方式:
(1).协同式:线程的执行时间由线程本身来控制,线程任务执行完成之后主动通知系统切换到另一个线程去执行。
优点:实现简单,线程切换操作对线程本身是可知的,不存在线程同步问题。
缺点:线程执行时间不可控制,如果线程长时间执行不让出CPU执行时间可能导致系统崩溃。
(2).抢占式:每个线程的执行时间有操作系统来分配,操作系统给每个线程分配执行的时间片,抢到时间片的线程执行,时间片用完之后重新抢占执行时间,线程的切换不由线程本身来决定。
优点:线程执行时间可控制,不会因为一个线程阻塞问题导致系统崩溃。
当前JDK的多线程是抢占式的多线程系统,但是可以通过设置线程优先级和改变线程的执行时间分配概率。
注意:由于JDK的线程优先级和操作系统的线程优先级不是一一对应的,因此建议只使用1(最低优先级)、5(正常优先级)和10(最高优先级)这三个优先级。
另外,线程优先级只是操作系统给线程分配执行时间的概率大小,不是绝对的。
Java中线程的状态即调度关系如下: