1:介绍
java虚拟机支持多线程运行。线程代表的就是Thread class。对用户来说创建线程的唯一办法就是创建一个Thread对象;每一个线程都和一个Thread对象关联。Thread对象调用start()方法就启动了相应的线程。
线程的表现,尤其是当不能正常同步的时候,会变得混乱和违法直觉。这个规范说明(指:jsr133)描述了java语言里多线程编写的语义定义;它包含了这样的规则:即定义共享内存的读线程能够读到被共享内存的写线程写入的哪些数据。尽管这个规范类似于不同硬件架构的内存模型,但这是一个java内存模型的语义定义(semantics )。
这些语义并不是描述一个多线程程序是如何执行的。而是描述了多线程程序允许展现的行为。(the behaviors that multithreaded programs are allowed to exhibit.)
(Any execution strategy that generates only allowed behaviors is an acceptable execution strategy. )任何执行策略只要产生的是这个规范允许的行为那么它就是可以接受的执行策略。
1.1 Locks
多线程之间的沟通有很多种机制。最基础的机制就是同步(synchronization),使用monitors来实现的机制。每个对象关联一个监听器(monitor),一个线程可以锁定或者解锁它。同一时间只有一个线程可以持有一个监视器的锁。任何其他试图锁定已经被锁定的监视器的线程都会被阻塞直到他们持有这个监视器的锁。
一个线程t可以锁定一个特定的监听器很多次;每一个解锁操作对应一次锁定操作,还原对象状态。
同步声明(同步块)计算出一个对象的引用;然后尝试执行一个对该对象监视器的锁定操作并且直到锁定完成之前不会继续执行。锁定操作执行后,同步声明中的操作才会被执行。如果同步块的执行完成,不管是正常结束还是异常终端,同一个监视器锁的解锁操作都会自动执行。
一个同步方法在调用时会自动执行一个锁定操作;在锁定操作完成之前同步方法的内容不会被执行。如果这个方法是一个实例方法,它锁定的是它被调用实例关联的监视器(monitor)(也就是:方法体执行过程中被称为this的这个对象)。如果是静态方法,它锁定的就是方法声明所在的类的类对象对应的监视器。同样的,一旦方法体执行结束,不管是正常的还是异常终止,解锁操作都会在同一个监听器上自动执行。
这份声明文档(指本文)既不提供也不需要检测死锁的条件。那些多线程持有(直接或间接)多个对象的锁的程序应该使用常用的避免死锁的方法,如果有必要的话,就创建更级别的锁原语。
其他机制,例如读取和写入Volatile以及在java.util.concurrent 包中的类,提供了正确同步的可选方法。
1.2 Notation in Examples
Java内存模型并不是根本上基于java语言的面相对象特性的。为了例子的简洁、简易,我们只展示代码片段而忽略类和方法的定义,或者明确的非关联性。大多数例子是由两个或多个包括访问本地变量,共享全局变量或者一个对象的字段实例的状态的线程组成。我们一般使用类似r1或者r2这样的变量名来表明一个方法或者一个线程的本地变量。这些变量是不能够被其他线程访问的。
3:Informal Semantics
当代码被重排序的时候,一个程序必须被正确的同步来避免多种类型的违反直觉的行为发生。使用正确的同步不能保证程序里上述的行为是正确的。但是,使用它允许一个程序员以一种简单的途径来推理出一个程序的可能行为;一个正确同步的程序的行为是极少依赖可能的重排序的。没有正确的同步,非常奇怪的、令人迷惑的和匪夷所思的行为就可能会出现。
有两个关键的办法来理解一个程序是否正确同步了:
1:Conflicting Accesses (访问冲突)
两个访问(读取或者写入)同一个共享字段或者数组元素,如果其中至少有一个访问时写入那么就被称为冲突Conficting。
2:Happens-Before Relationship
两个行为如果是happens-before关系,可以排序。如果一个行为happens-before另一个行为