synchronized关键字和volatile关键字

Java中每个对象都能作为锁:
1.对于普通同步方法,锁是当前实例对象。
2.对于静态同步方法,锁是当前类的Class对象。
3.对于同步方法块,锁是synchronized括号里面配置的对象。


那么,同步方法和同步代码块的实现方式是怎样的呢?
1.同步代码块是使用monitorenter和monitorexit指令实现的;
2.同步方法是隐式的,虚拟机通过方法常量池里面的方法表结构(method_info structure)里面的ACC_SYNCHRONIZED访问标志来区分一个方法是不是同步方法(Java 虚拟机规范 2.11.10节)。

 

那么synchronized的锁存在哪里呢?

Java对象头里面,如果是32位的JVM,对象头的Mark Word存储对象的HashCode(25bit),分代年龄(4bit),锁标记位(2bit),是否偏向锁(1bit)。

锁标志位如果是00,表示轻量级锁,01表示偏向锁,10表示重量级锁,11是GC标记了。

 


volatile关键字(volatile,易挥发的,不稳定的):

volatile可以说是轻量级的synchronized,它保证了共享变量的可见性。即当一个线程修改一个volatile修饰的共享变量时,另一个线程能立刻读取到最新的值。
从内存可见性的角度来看,volatile变量的写相当于退出同步代码块,volatile变量的读相当于进入同步代码块。
也可以这样认为,
1.线程A写一个volatile变量,实质是线程A对接下来将要读这个volatile变量的线程发出了消息(什么消息呢?对共享变量修改的消息)。
2.线程B读一个volatile变量,实质是线程B接收了之前某个线程发出的消息。
3.线程A写一个volatile变量,随后线程B读这个变量,其实就是线程A通过主内存向线程B发送消息。

 

那么volatile关键字是如何实现可见性的呢,Lock前缀指令,该指令在多核处理器会做两件事情:

1.将当前处理器缓存行的数据写回到系统内存;

2.使其他CPU里面缓存了该内存地址的数据无效。

那么是如何使其他CPU无效的呢?
MESI协议。
什么是MESI协议?
缓存一致性协议管理缓存行的状态,以防止数据不一致或者丢失更新。MESI表示,modified,exclusive,shared,invalid四种状态。
modified状态的缓存行,表示缓存的是最新的数据。
exclusive状态也是最新的数据,但是此数据还没有被相应的CPU修改过。
shared状态的缓存行可能被复制到至少一个其他CPU缓存中。
invalid状态的缓存行是空的

 

重排序:
为了提高性能,编译器和处理器通常会对指令进行重排序,简单来说重排序就是编译器和处理器为了优化程序性能而对指令序列进行处理的一种手段。
重排序分为编译器重排序和处理器重排序,处理器重排序又分为指令级并行排序和内存系统的重排序;
处理器重排序是通过插入特定的内存屏障(Memory Barriers)指令来禁止特定类型的处理器重排序。
JMM把内存屏障指令分为4类:
1.LoadLoad Barriers --->Load1;LoadLoad;Load2
2.StoreStore Barriers --->Store1;StoreStore;Store2
3.LoadStore Barriers ---> Load;LoadStore;Store
4.StoreLoad Barriers ---> Store;StoreLoad;Load

再来看看Happens-Before规则:

1.程序顺序规则:如果程序中A操作在B操作之前,那么在线程中A操作将在B操作之前执行。
2.监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。
3.Volatile变量规则:对Volatile变量的写入操作必须在对该变量的读操作之前执行。
4.线程启动规则:在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行。
5.线程结束规则:线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false。
6.中断规则:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行。
7.终结器规则:对象的构造函数必须在启动该对象的终结器之前执行完成。
8.传递性:如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。

volatile的重排序规则:
1.当第一个操作是volatile读时,不论第二个操作是什么都不能重排序。
2.当第一个操作是volatile写时,第二个操作是volatile读时,不能重排序。
3.当第二个操作是volatile写时,不论第一个操作是什么,不能重排序。


JMM采用保守策略插入内存屏障:
1.在每个volatile写前面插入一个StoreStore屏障。
2.在每个volatile写后面插入一个StoreLoad屏障。
3.在每个volatile读后面插入一个LoadLoad屏障。
4.在每个volatile读后面插入一个LoadStore屏障。

posted @ 2017-11-05 15:35  emoji的博客  阅读(350)  评论(0编辑  收藏  举报