synchronized和volatile底层实现

synchronized(锁对象)

Synchronized的作用
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据

一、synchronized的特性

缓存导致的可见性问题,线程切换带来的原子性问题,编译优化带来的有序性问题

  • 1.1 原子性

所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++、i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性。

被synchronized修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的stop()方法),即保证了原子性。

注意!面试时经常会问比较synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。

  • 1.2 可见性

可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。

synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。

而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。

  • 1.3 有序性

有序性值程序执行的顺序按照代码先后执行。

synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。


synchronized的底层实现原理

1.同步方法

  1. 方法级的同步是隐式的,无须通过字节码指令来控制,JVM可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。
  2. 当方法调用的时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否被设置
  3. 如果设置了,执行线程就要求先持有monitor对象,然后才能执行方法,最后当方法执行完(无论是正常完成还是非正常完成)时释放monitor对象。
  4. 在方法执行期间,执行线程持有了管程,其他线程都无法再次获取同一个管程。

2.同步代码块

  1. synchronized关键字经过编译之后,会在同步代码块前后分别形成monitorenter和monitorexit字节码指令
  2. 在执行monitorenter指令的时候,首先尝试获取对象的锁
  3. 如果这个锁没有被锁定或者当前线程已经拥有了那个对象的锁,锁的计数器就加1
  4. 在执行monitorexit指令时会将锁的计数器减1,当减为0的时候就释放锁。
  5. 如果获取对象锁一直失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

上面两种的最终实现 (lock cmpxchg 指令
cmpxchg = cas修改变量值

cas机制:
在这里插入图片描述ABA问题:加版本号或者给对象加boolean值判断是不是被修改过

synchronized的硬件层面实现原理
lock指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线的方式)
因为锁北桥信号相应速度更快 锁粒度更小



锁升级过程在这里插入图片描述

在这里插入图片描述

Object o = new Object();占16字节:
对象头12字节:包括markword 8字节,classPointer4字节
没有实例数据 所以instanceData 0字节
对齐填充区4字节(所有Java对象占用字节数必须是8的倍数)

  • int—4个字节
  • long–8个字节
  • double–8个字节
  • float–4个字节
  • short–2个字节
  • char–2个字节(Unicode编码)
  • Boolean–1个字节
  • byte–1个字节
  • java对象–4个字节(所以指针也是4个字节)

(JDK1.6版本升级) **无锁 - 偏向锁 - 轻量级锁 (自旋锁,自适应自旋)- 重量级锁**
  • 无锁:对象刚new出来 没有加锁
  • 偏向锁:把markword的线程ID改为自己线程ID的过程 偏向锁不可重偏向 批量偏向 批量撤销
  • 轻量级锁:任何竞争都会使偏向锁升级为轻量级锁,Java的线程是映射到操作系统原生线程之上的,阻塞或唤醒线程要操作系统从用户态和内核态之间切换, 线程刚刚进入阻塞状态,这个锁就被其他线程释放了,则需要操作系统来唤醒,浪费资源。轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令。
  • 重量级锁:当cas自旋10次以上或cpu核心数自旋至cpu总核心数的1/2,则自动升级为重量级锁。(总不至于一直在while循环消耗资源吧)CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间

如果计算过对象的hashCode,则对象无法进入偏向状态!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值