1 volatile
1.1 volatile应用
保证共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能及时读到这个修改的值。count++无法保证count的“可见性”。因为涉及两个操作:1)从缓存区读取count值 2)count+1之后写回内存。
public class VolatileDemo {
private int count;
public int increase() {
try {
Thread.sleep(300);
return count++;
} catch (InterruptedException e) {
throw new RuntimeException();
} finally {
// lock.unlock();
}
}
public static void main(String[] args) {
VolatileDemo vd = new VolatileDemo();
new Thread(new Runnable() {
@Override
public void run() {
while (true)
System.out.println(vd.increase());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true)
System.out.println(vd.increase());
}
}).start();
}
}
结果:
我们加上volatile关键字之后的结果:
不存在重复
1.2 实现原理
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存数据读到内存缓存后进行操作。但是操作系统不知道何时回写到内存。如果对声明了volatile的变量进行写操作,JVM将会告诉处理器何时把变量值写回到内存。但是,就算写回到内存。如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存数据是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期了。
1)将当前处理器缓存行的数据写回到系统内存
2)这个写回到内存的操作会使其他CPU里缓存了该内存地址的数据无效
对于声明了volatile的变量,JVM会向处理器发送一条Lock前缀的命令,将这个变量锁在缓存中的数据写回到内存,实现了1)。2)的实现依赖于缓存一致性协议,每个处理器通过“嗅探”总线上传播的数据来检查自己缓存的值是否过期。
1.3 缓存一致性协议(MESI)
MESI是代表了缓存数据的四种状态的首字母,分别是Modified、Exclusive、Shared、Invalid)
M :我已经对缓存中的数据进行了修改,你们有要读取该数据的,那我就得先写回去了(保证读的是最新的数据)。
E :我没有对缓存中的数据进行修改(和内存中的值相等),你们有人也读取了该数据,那我就切换成共享。
S :有人对共享数据进行了修改,那我的数据不是最新的了(无效了),我要重新读。
通过以上机制可以使得处理器在每次读写操作都是原子的,并且每次读到的数据都是最新的。
2 Sychronized的加锁方式
为什么我们把对象称之为锁呢?
因为,我们使用的锁的信息都放在对象的对象头里面。
3 Sychronized的实现原理
执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。 任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态。
该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。
4 Java对象头
我们知道锁信息是存放在Java对象头的,那么到底存放了哪些信息呢?
Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位。32为JVM Mark Word默认存储结构为
锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。对象的MarkWord变化为下图:
也就是Java对象头可以告诉我们很多的信息,例如,是哪种类型的锁,锁对应的ID,锁的分代年龄等信息。