第2章,Java并发进制的底层原理

第2章,Java并发机制的底层原理

深入理解Java并发之synchronized实现原理

Java代码在编译后会变成Java字节码,字节码被类加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。

可见性、原子性、有序性、实现并发机制的三大特性。

1、volatile原理

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性是指一个线程修改共享变量的时候,另外一个线程可以看到修改后的值。

如果一个字段被声明成volatile的,Java线程内存模型确保所有线程看到这个变量的值是一致的。

volatile不具备原子性,不足以保证线程安全。

使用场景:Java线程安全之volatile关键字
1、对变量的写操作不依赖于当前值。
2、该变量没有包含在具有其他变量的不变式中。

volatile比synchronized的使用成本低,因为它不需要上线文的切换与调度。

volatile底层的cpu指令中有lock指令,lock前缀的指令在多核处理器下会引发两件事情:
大前提:每个处理器对同一块内存会有自己的缓存。
1、它可以保证将当前处理器缓存行中的数据写回到系统内存。
2、这个写回内存的操作,会使其他cpu里缓存了该内存地址的数据失效。每个处理器通过嗅探在总线上传播的数据来检查自己缓存值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

2、synchronized原理

Synchronized,jdk1.6经过各种优化后,已经没有那么重了,为了减少获得锁与释放锁带来的性能消耗,引入了轻量级锁与偏向锁。

锁从低到高依次为:无锁,偏向锁,轻量级锁,重量级锁。锁只能升级不能降级,这种策略也是为了保证获得锁与释放锁的效率。

synchronized实现同步的基础,java中的每一个对象都可以作为锁。
1、普通的同步方法,锁的是当前的实例对象。
2、静态同步方法,锁的是当前类的class对象。
3、同步方法块,锁的synchronized括号里配置的对象。

JVM基于进入和退出Monitor对象来实现方法同步与代码块同步。

代码块是使用monitorenter(插入到同步代码块的开始位置)和monitorexit(插入到同步代码块的结束与异常位置)指令来实现的。

任何一个对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

锁存在于对象头的mark word中。

synchronized用的锁是存在Java对象头里的。

MarkWord里默认存储对象的HashCode,分代年龄,和锁标记位。

以下的对象头存储的数据内容会随着锁标志位的变化而变化。

3、Java中的锁

Jdk1.6以后,引入了偏向锁与轻量级锁,锁的状态从低到高时无锁,偏向锁,轻量级锁,重量级锁。锁只能升级不能降级,这是为了提高获得锁和释放锁的效率。

偏向锁

1、获取:首先判断要获取的对象的对象头中是否线程的ID,如果有,直接获取偏向锁,否则,先判断对象头中的锁是否为偏向锁,如果是,用CAS来修改偏向锁中的线程ID指向自己,否则根本不是偏向锁的话,需要CAS来竞争锁。
2、撤销:等到竞争出现才释放锁,当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。需要等到全局安全点,才可以撤销偏向锁,在全局安全点,没有正在执行的字节码。
3、关闭:通过命令关闭偏向锁,进入轻量级锁状态。

轻量级锁

1、加锁:线程将对象头中的markWord复制到自己的栈帧中,称作DisplacedMarkWord,然后用CAS更新对象头中的栈帧指向自己,失败的话,就使用自旋来获取锁。
2、解锁:线程使用DisplacedMarkWord,CAS替换对象头,成功就解锁,否则说明当前锁存在竞争,锁就膨胀为重量级锁。

锁的优缺点比较

优点 缺点 适用场景
偏向锁 执行时间快 竞争锁时多了撤销锁的代价 适用一个线程访问同步块的场景
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 始终得不到锁的线程会自旋获取所锁,消耗CPU 追求响应时间,同步块执行时间很快
重量级锁 线程竞争不会适用自旋,不消耗CPU 线程阻塞,响应时间慢 追求吞吐量,同步块执行时间比较长

4、获取Class对象的三种方式

1、class.forName方法:System.out.println(Class.forName(“chapter01.Dog”));
2、对象.getClass方法:System.out.println(dog1.getClass());
3、类.class变量:System.out.println(Dog.class);

5、CAS

CAS:compare and swap,比较并交换,传递进去一个旧值,一个新值,当目标是旧值的时候,才将目标更新成新值。

处理器实现原子操作
1、使用总线锁保证原子性:处理器锁住总线,其他处理器不可以操作共享内存。
2、使用缓存锁保证原子性:只锁住对应缓存中的内容,其他内存地址中的内容仍然可以访问。

以下情况无法使用缓存锁定,只能使用总线锁定:
1、当操作的数据不能缓存在处理器中,或者是要操作的数据缓存在不止一个缓存行中。
2、某些处理器不支持缓存锁定。

Java实现原子操作

1、自旋CAS操作:AtomicBoolean,AtomicInteger,AtomicLong,还有原子加1,减1等。CAS操作的三大问题:

  • 只能操作一个原子数据:把多个对象组合成一个来操作。
  • ABA问题:使用版本号来解决此问题。
  • 循环时间长,开销大:如JVM支持处理器的pause命令,则可解决此问题。

2、锁:JVM内部实现了很多的锁机制,如偏向锁,轻量级锁,互斥锁等,除了偏向锁,其他的锁内部也是使用了自旋CAS来获取锁和释放锁的。

发布了95 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/jiangxiulilinux/article/details/104851879