十四、Java高级特性(volatile、synchronize

2021-06-07  本文已影响0人  大虾啊啊啊

1、Java内存模型(JMM)

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,我们叫做工作内存。线程对共享变量的所有操作都是工作内存中进行,它首先会从主内存中读取数据到工作内存,然后在工作内存中做相应的操作,而不能直接操作工作内存中的数据。并且线程之间的工作内存也不能互相访问,必须经过主内存作为中间件。

image.png

2、可见性

可见性指的是当有多个线程访问共享变量的时候,一个线程修改了值,其他线程可以立即看得到。当一个线程需要对共享变量的值进行修改的时候,首先将主内存中的数据读到工作内存,然后在工作内存进行修改,接着才会刷新到主内存,并且刷新到主内存的过程有一定的时间差,不会立即刷新到主内存。这样就会产生多个线程访问共享变量的时候拿到的不是最新的值。这种情况我们可以通过使用volatile关键字或者加锁的方式来解决

3、原子性

原子性:即一个操作或者多个操作要么全部执行并且执行过程不被任何因素打断,要么不执行。
我们知道CPU通过分配时间片给线程,让每个线程在自己的时间片范围内工作。当时间片完了的时候就会产生上下文切换,切换到下一个线程继续工作。那么这个切换过程可能会在CPU的任何一条指令,当我们一个线程中执行了一个 count++的操作,可能包含了三个cpu指令,有可能就在第二个指令的时候cpu切换到别的线程工作,这样就不能保证一个coun++的原子性。这种情况我们可以通过加锁的方式来解决,例如synchronized。

4、volatile详解

volatile保证了可见性,也就是通过volatile关键字修饰的共享变量,当一个线程在工作内存中修改了其值之后,会立即刷新到主内存,当后面的线程每次读的时候,都能读到最新的值,但是volatile不能保证原子性,因为volatile只是保证了线程修改共享变量立即刷新到主内存,但是不能保证在它还没修改成功的时候,其他线程可能已经从主内存中读取值到自己的工作内存中进行修改。所以可以通过加锁的方式来保证原子性。

5、volatile的实现原理

volatile关键字修饰的变量会存在一个“lock:”的前缀。
Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。
同时该指令会将当前处理器缓存行的数据直接写会到系统内存中,且这个写回内存的操作会使在其他CPU里缓存了该地址的数据无效

6、synchronized的实现原理

Synchronized在JVM里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。
对同步块,MonitorEnter指令插入在同步代码块的开始位置,而monitorExit指令则插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit。总的来说,当代码执行到该指令时,将会尝试获取该对象Monitor的所有权,即尝试获得该对象的锁:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
对同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来实现,相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。
JVM就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

7、synchronized和ReentrantLock的区别

(1) ReentrantLock
线程可以重复进入任何一个它已经拥有的锁所同步着的代码块,synchronized、ReentrantLock都是可重入的锁。在实现上,就是线程每次获取锁时判定如果获得锁的线程是它自己时,简单将计数器累积即可,每 释放一次锁,进行计数器累减,直到计算器归零,表示线程已经彻底释放锁。
底层则是利用了JUC中的AQS来实现的。
(2)synchronized
synchronized (this)原理:涉及两条指令:monitorenter,monitorexit;再说同步方法,从同步方法反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来实现,相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。
JVM就是根据该标示符来实现方法的同步的:当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

上一篇 下一篇

猜你喜欢

热点阅读