原子性:一个或多个操做要么所有执行成功要么所有执行失败;java
可见性:当多个线程访问同一个变量时,若是其中一个线程对其做了修改,其余线程能当即获取到最新的值;编程
有序性:程序执行的顺序按照代码的前后顺序执行(处理器可能会对指令进行重排序);缓存
CPU愈来愈快,主存逐渐跟不上cpu的频率,cpu须要等待主存浪费资源。因此cache的出现主要为了解决cpu和内存之间频率不匹配的问题。多线程
但cache也带来了新的问题,并发处理的不一样步(不过能够经过总线锁和缓存一致性来解决)。并发
在执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。app
一、编译器优化的重排序:编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。jvm
二、指令级并行的重排序:现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。性能
若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。优化
三、内存系统的重排序:因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。以下图:spa
as-if-serial语义的意思是:无论怎么重排序(编译器和处理器为了提升并行度),单线程程序的执行结果不能被改变。
编译器、runtime和处理器都必须遵照as-if-serial语义。
为了遵照as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操做作重排序。数据依赖关系以下图所示:
单线程状况下,控制依赖关系的重排序,不影响最终结果。多线程状况下,则可能会破坏程序的意图。
一、对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是全部的编译器重排序都要禁止)。
二、对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barriers,Intel称之为MemoryFence)指令,
经过内存屏障指令来禁止特定类型的处理器重排序。
一、在每一个volatile写操做前插入StroreStore屏障
二、在每一个volatile写操做前插入StroreLoad屏障
三、在每一个volatile读操做前插入LoadLoad屏障
四、在每一个volatile读操做前插入LoadStore屏障
volatile
是轻量级的synchronized
,它在多处理器开发中保证了共享变量的“可见性”。volatile
不会引发上下文的切换和调度(禁止指令重排),执行开销更小。
可见性:对一个volatile
变量的读,老是能看到(任意线程)对这个volatile
变量最后的写入。
原子性:对任意单个volatile
变量的读/写具备原子性,但相似于volatile++
这种复合操做不具备原子性。
1. 使用volitate修饰的变量在汇编阶段,会多出一条lock前缀指令(ACC_VOLATILE)
2. 它确保指令重排序时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面;
即在执行到内存屏障这句指令时,在它前面的操做已经所有完成
3. 它会强制将对缓存的修改操做当即写入主存
4. 若是是写操做,它会致使其余CPU里缓存了该内存地址的数据无效
volatile的写-读与锁的释放-获取有相同的内存效果。
这里A线程写一个volatile变量后,B线程读同一个volatile变量。
A线程在写volatile变量以前全部可见的共享变量,在B线程读同一个volatile变量后,将当即变得对B线程可见。
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
注:关于volatile变量重排序,严格限制编译器和处理器对volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具备相同的内存语义。
JMM重排序分为编译器重排序和处理器重排序,JMM会限制这两种类型的重排序类型来保证volatile的内存语义
一、第二个操做是volatile写时,第一个操做不论是什么,都不能重排序
二、第一个操做是volatile读时,第二个操做不论是什么,都不能重排序
三、第一个操做是volatile是写,第二个操做是volatile是读,不能重排序
volatile本质是在告诉jvm当前变量在寄存器(工做内存)中的值是不肯定的,须要从主存中读取;
synchronized则是锁定当前变量,只有当前线程能够访问该变量,其余线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则能够保证变量的修改可见性和原子性
volatile不会形成线程的阻塞;synchronized可能会形成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量能够被编译器优化
synchronized 是JVM实现的一种锁,其中锁的获取和释放分别是monitorenter 和 monitorexit 指令,该锁在实现上分为了偏向锁、轻量级锁和重量级锁,
其中偏向锁在 java1.6 是默认开启的,轻量级锁在多线程竞争的状况下会膨胀成重量级锁,有关锁的数据都保存在对象头中。
对于普通同步方法,锁是当前实例对象;
对于静态同步方法,锁是当前类Class对象;
对于同步方法块,锁是Synchronized括号里配置的对象;
加了 synchronized 关键字的代码段,生成的字节码文件会多出 monitorenter 和 monitorexit 两条指令(利用javap -v *.class字节码文件)
加了 synchronized 关键字的方法,生成的字节码文件中会多一个 ACC_SYNCHRONIZED 标志位,当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,
若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象。
其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需经过字节码来完成。
一、会让没有获得锁的资源进入Block状态,争夺到资源以后又转为Running状态,这个过程涉及到操做系统用户模式和内核模式的切换,代价比较高。
二、Java1.6为 synchronized 作了优化,增长了从偏向锁到轻量级锁再到重量级锁的过分,可是在最终转变为重量级锁以后,性能仍然较低。
JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义。
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所作修改的)消息。
线程B获取一个锁,实质上是线程B接收了以前某个线程发出的(在释放这个锁以前对共享变量所作修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A经过主内存向线程B发送消息。