Java并发

281 阅读3分钟

Volatile

背景

为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是引入了多级缓存就存在缓存数据不一致的问题。

原理

对于volatile变量,当对volatile变量进行写操作时,JVM就会向处理器发送一条lock修饰的指令,写完后再将这个缓存中的变量写回到系统主存中去。

但是,写回到主存中后,其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致协议。

缓存一致协议:每个处理器通过“嗅探”在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器需要对这个数据进行读写操作时,会强制重新从系统内存中把数据读到处理缓存中。

所以,如果一个变量被volatile修饰的话,在每次数据变化之后,其值会被强制刷入主存。而其他的处理器由于遵守缓存一致性协议,也会把这个变量的值重新刷入到缓存中。这就保证了volatile在并发编程中,其值在多个缓存中是可见的。

Synchronized

背景

造成线程安全的主要原因有两点,

  • 存在共享资源(临界资源)
  • 多条线程共同操作共享数据

应用方式

  • 修饰实例方法,进入代码前要获得当前实例的锁
  • 修饰静态方法,进入代码前要获得当前类对象的锁
  • 修饰代码块,进入代码库前要获得给定对象的锁

底层原理

反编译代码块

反编译Synchronized修饰的代码块后的结果会出现monitorenter和monitorexit指令,这两条指令的作用是:

  • monitorenter:每个对象都有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权限,过程如下:

1、如果monitor的进入数为0,则线程进入monitor,然后将进入数设置为1,表明该线程为monitor的所有者。

2、如果线程已经占有该monitor,则重新进入,且monitor进入数加1。

3、如果其他线程占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再尝试获取monitor的所有权。

  • monitorexit:执行monitorexit的线程必须是引用对象所对应的monitor所有者。执行指令时,monitor的进入数减1,如果减1后为0,则线程释放monitor的权限,不再是monitor的所有者。其他被这个monitor阻塞的线程可以尝试获取这个monitor的所有权。
  • 注:wait/notify等方法也依赖于monitor对象。

反编译同步方法

反编译同步方法并没有通过指令monitorenter和monitorexit来完成,但是相对于普通方法,常量池中多了ACC_SYNCHRONIZED标识符。JVM就是根据该标识符来实现方法的同步的:当方法调用时,调用指令会检查ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程先获取monitor,获取成功后才能执行方法体,方法体执行完再释放monitor。在方法执行期间,其他任何线程都无法获取同一个monitor对象。

参考资源

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

2、Java并发编程:Synchronized及其实现原理