二、Java并发-volatile和synchronized的底层实现及不同和CAS的原理

在多线程并发编程中synchronized和volatile都扮演的重要的角色,接下来我们来学习一下他们两个的相关知识及底层原理:

1.volatile的定义
定义:Java语言允许线程访问共享变量,为了确保共享变量能被准确一致地更新,线程应该确保通过排他锁单独获得这个变量
于是Java提供了volatile,某些情况比锁更方便。如果一个字段被声明为volatile,Java内存模型(JMM)确保所有线程看到这个变量的值是一致的。

2.volatile如何实现可见性的
(1)JVM向处理器发送的Lock前缀的指令,会引起处理器缓存回写到内存
(2)一个处理器缓存回写到内存会导致其他处理器的缓存无效。

3.synchronized的对象锁和类锁的介绍和不同
(1)对象锁:Java中任何一个对象都可以作为锁,当修饰普通同步方法和同步方法块时应该使用对象锁
(2)类锁:Java中对于修饰静态同步方法时应该使用类锁(XX.class)
(3)区别:使用对象锁时,一个线程正在访问对象的同步代码块,另一个线程访问会被堵塞。而使用类锁时,同一个类的不同对象可以同步访问代码块

4.synchronized的底层实现
(1)synchronized在JVM中基于进入和退出monitor监视器对象来实现方法同步和代码块同步,指令为monitorenter和monitorexit实现的。
(2)synchronized用的锁是存在Java对象头里的MarkWord里,存储了对象的Hashcode,锁标记位等
在这里插入图片描述
5.synchronized锁的四种状态及不同
膨胀方向:无锁->偏向锁->轻量级锁->重量级锁
升级:这几个状态会随着竞争状况逐渐升级。
降级:当JVM进去SafePoint安全点时,会检查是否有闲置的monitor进行降级

(1)偏向锁:
大多数情况下,锁不仅不存在多线程竞争,而且总是由一个线程多次获得,**为了让同一线程获得同一锁的代价更低引入了偏向锁,当一个线程获得锁时,会在对象头和栈帧中锁记录里储存偏向的线程ID,以后就可以直接测试是否储存着指向当前线程的偏向锁。**如果有其他线程竞争锁,才释放锁,将对象头设置为无锁状态
在这里插入图片描述
(2)轻量级锁
加锁:JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,然后线程会尝试使用CAS将对象头中的MarkWord替换为指向锁的指针。成功,当前线程获取锁,如果失败,就尝试自旋来获取锁。如果该线程一直自旋获取锁失败就会膨胀成重量级锁,线程阻塞。

解锁:会使用原子的CAS操作将栈帧中的以前复制MarkWord的替换回对象头,成功表示无竞争(是轻量级锁,另外一个线程还在自旋)直接释放,失败(因为这个时候锁已经膨胀成重量级锁),那么释放之后唤醒被阻塞的线程。
在这里插入图片描述
总结:
在这里插入图片描述
6.什么是自旋锁和自适应自旋锁
自旋锁:如果持有锁的线程能很短时间内释放锁,那么竞争锁的线程就不需要进入阻塞挂起状态,而是等一会(自旋),这样能避免用户线程和内核线程的切换消耗,但是如果超过一定时间仍未得到,还是会进入阻塞

自适应自旋锁:自选的次数不再固定,由前一次在同一个锁上的自旋时间及锁拥有者的状态决定

7.synchronized和volatile的区别
(1)volatile仅能实现变量的可见性,不能保证原子性,synchronized可以保证变量的可见性和原子性;
(2)volatile不会造成线程阻塞,synchronized可能会造成线程阻塞(因为volatile只是将当前变量的值及时告知所有线程(可见性),而synchronized是锁定当前变量不让其它线程访问(可见性和原子性));
(3)volatile仅能使用在变量上,synchronized可以使用在变量和方法上;

8.什么是CAS?
CAS是Compare And Swap的简称,是CPU的一个原子操作,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

9.CAS的应用和存在的问题
应用:从Java1.5开始,JUC的atomic包下的类都是使用的CAS技术来实现的支持原子操作类,简称原子包装类,提供的方法都是原子操作(之后再仔细讲解)

存在的问题:
(1)ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

(2)循环时间长开销大:自旋CAS如果长时间不成功,就会给CPU带来很大开销

(3)只能保证一个共享变量的原子操作:对一个共享变量进行操作可以用循环CAS保证原子操作,多个就不行,可以用锁。

10.Java中如何实现原子操作
可以使用锁和循环CAS来实现(循环进行CAS操作直到成功),单个共享变量两者都可以,多个只能用锁

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值