JSR-133内存模型手册
1.介绍
JVM支持多种线程的执行,Threads代表的是线程类,位于java.lang.Thread包下,唯一的方式就是为用户在这个类下的对象创建线程,每一个线程关联着一个对象,一个线程将在start()调用时声明这个线程对象
在针对线程同步错误的情况下,手册描述了多线程程序的语义,包括了值位于只读内存在线程中更新的情况,这些都是基于内存模型下的语义规范,而这个文档主要讲述了内存模型.
这些语义并不是结束如何执行一个多线程程序,而描述多线程所表现的原理机制.
1.1 Locks
线程之间最基本的通信通过同步(synchronized)实现监控,每一个对象与一个监视器关联锁的状态,只有一个线程可以在监视器上控制一个锁,其他的线程会进入阻塞(blocked)状态,直到可以获取锁,有关锁的概念可以通过进行了解.
一个线程可以锁定一个特定监视器多次,每个逆转的锁会影响一个锁操作.
synchronized语句会计算对象的引用,然后尝试执行一个锁对象直到锁操作完成,在锁操作执行完后,synchronized会执行,当一个主方法执行完成后,会自动操作同一个监视器.
一个同步方法在调用后会自动执行锁方法,直到锁完成操作,当方法是一个是实例方法时,他会将调用实例所关联的监视器锁住,当方法是静态的,会将已经定义方法的类对象所关联的监视器锁住.如果执行方法主体已经完成,那么监视器会自动执行同一个操作.
语义并没有阻止和监测死锁发生,程序在遇到线程发生死锁条件,可以通过以下方式进行避免
- 互斥条:一个资源每次只被一个进程使用
- 请求于保持条件:一个进程因请求资源而阻塞时,对以获取的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完后,不能强行剥夺
- 循环等待:将系统中所有资源设置标记来避免死锁
而在其他机制中,java提供了java.util.concurrent包来替代同步机制,concurrent详情可以查看进行了解
2.非正式语义
一个程序必须正确的在代码重排序之前避免错误的同步行为,synchronized不能保证绝对的准确性,但是他所带给程序的好处是可以减少程序的重排序行为.
有2个关键的思想来确定同步的正确性
Conflicting Accesses 两个读写访问同一个字段或者数组元素会保证至少有一个访问的操作
Happens-Before 关系 两个操作利用H-b来指定顺序,当一个动作处于h-b状态,那么第一个会在第二个之前显示,h-b关系不会必须发生在java平台中,H-B关系主要强调了两个动作方法之间的排序方式以及定义的方法.下面是H-B所包含的规则:
- 每个线程操作会对后续的线程进行Happens-Before
- 一个未加锁的监视器会对后续的监视器进行Happens-Before
- 一个Volatile字段的写操作会对后续的Volatile进行Happens-Before
- 在启动线程里start()方法的操作是都是Happens-before规则
- 在线程中发生Happens-before规则时成功后在线程中返回join()方法
- 当a H-B b ,b H-B c 则 a H-B c
3.1 顺序一致性
顺序一致性用于保证程序执行的可见性和排序,在一个顺序一致性行为时,在一个执行顺序一致,有一个全序个人行为(如读和写)这是符合程序的顺序。
每一个操作都具有原子性,每个线程都立即可见,如果程序没有数据竞争,那么所有执行的程序都具有顺序一致性,而作为之前提到的顺序一致性和数据竞争都需要知道原子的存在性.
当我们对内存模型使用顺序一致性时,许多编译器在优化时我们都要讨论他的合法性.
有顺序一致性所讨论的,我们可以使用它来提供一个重要的澄清关于数据竞争和正确同步的程序,竞态条件发生在处于相互冲突的同步排序下,一个程序在正确的同步状态下会消除竞态条件,所以,程序员只需要思考程序在顺序一致时是否处于正确的同步
3.2 Final字段
Final字段被定义为不可改变,详细的final语义可以清楚地知道与普通字段的区别,特别是在编译器具有同步屏障(synchronization barriers)去声明未知的方法,相应的,能保证final字段缓存不会重新加载.
Final字段也可以是线程安全的不可变对象保持不同步状态,一个线程安全的不可变对象被认为是不可变的,即使一个数据竞争时会在线程中引用不可变对象,这可以保证数据的安全.
Final字段必须保证不可变,一个对象在构造完成后完成初始化,线程只可以看到已经完成初始化后的final对象
在为构造器设置final字段时,不要其他地方引用该对象,线程会在构造函数完成时被看见,这会导致线程会只接收到正确构造版本的final字段,使数据保持不正确性
下面这个例子将展示普通字段与final字段的区别
4 什么是内存模型?
内存模型的被描述为一个程序和一个执行跟踪程序是程序的合法执行,在Java平台中,内存模型在每个只读跟踪程序每一个只读的监测和确认写操作是否遵循规则.
内存模型描述了程序中可能发生的行为,具体的表现是喜欢产生任意的代码,提供了大量的自由度来执行代码转换,包括重排遗迹不必要的同步.
再高级的行为中,非正式的内存模型可以显示当线程写操作显示另一个线程的规则中.
当我们在内存模型中使用"读"时,所指的是操作中的可读字段或者数组元素,在类似数组长度中读语义,调用虚拟方法中,不会影响数据竞争,jvm实现负责数据竞争不会导致错误,返回错误的数组长度或者虚方法调用导致的分割错误.
5.定义
共享变量/堆内存(Shared variables/Heap memory) 线程间可以互相共享的内存叫对内存,所有实例字段,静态字段以及数组元素都存储在堆内存中,我们用变量术语去引用全局字段和数组变量,一个方法中的局部变量在线程之间共享不会被内存模型所影响
线程内操作(Inter-thread Actions) 一个Inter-thread 操作可以执行监测 或影响另一个线程,Inter-Thread包含着读操作,共享变量的写操作和同步操作,加锁操作,还包含着与之交互的外部世界,和会导致线程进入无限循环.
我们不需要关心Inter-thread操作,正如前面提到的,每一个线程都具备Inter-thread语义,而任何Inter-thread都与执行的操作所关联,所有的操作都在程序中所操作的线程之间发生,关联的信息包括了
write read lock unlock
Program Order 所有的Inter-thread操作之间的线程,程序中线程的Program Order会给运行时环境足够的自由重排命令,此时的Program Order可以保证只能重排.
Intra-thread semantics 线程内语义时为单线程程序准备的,为了与线程的读操作得到预测,当线程的操作可以进行时,会在JLS定义线程上下文。
在每次确认线程是否为Intra-thread动作时,必须指定队列里的程序指向线程,如果为读操作,
通常将Intra-thread语义在从堆中读取值时隔离,他们在内存模型中确认存在.
Synchronization Actions 重量级锁包含锁,解锁,在volatile中读写操作,在开启线程时,任何在动作中开始或者在Synchronizeds-with中开启的节点都是重量级操作。
原文地址: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf