volatile、CAS、synchronized

volatile

  • 特性:

    • 保证可见性,不保证原子性
      • 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
      • 这个写会操作会导致其他线程中的volatile变量缓存无效。
    • 保证顺序性:禁止指令重排
      • a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
      • b.在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行
  • 单例模式需添加volatile来防止重排成a-c-b,造成其他线程误认为当前对象已创建好

    a. memory = allocate() //分配内存
    
    b. ctorInstanc(memory) //初始化对象
    
    c. instance = memory //设置instance指向刚分配的地址
    
为什么volatile不能保证原子性

Java中只有对基本类型变量的赋值和读取是原子操作

  • i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。

所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了

举例:

1、线程读取i

2、temp = i + 1

3、i = temp 当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6

4、然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1

以即使主存中的i值改变了,缓存一致性原则将B中的 i 变为新值,但是这个 i 值的改变也不会影响它将之前执行完的 i+1 得到的值赋给 i这一步,同样导致了最后的结果出错

CAS

  • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

  • 在思想上属于乐观锁

  • cas的实现是利用unsafe提供的原子性操作方法。unsafe是jvm提供的方法,我们可以利用unsafe来实现硬件级别的原子操作

  • ABA

    • 如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了

    • 解决办法:变量前面加上版本号

      从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
      
  • 循环时间长开销大

    • pause指令(如果JVM能支持)
      • 它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。
      • 它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率
  • 只能保证一个共享变量的原子操作

    从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作
    

synchronized

  • 使用方式
    • 在需要同步的方法签名中加入synchorized关键字
    • 对需要同步的代码块进行同步
  • 对象锁:锁住一个对象
    • synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁
  • 类锁:锁住该类的Class对象
    • 修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁

如何判断对象加锁

  • 在markword里面存值,通过markword来判断对象是否已经加锁
  • markword里面存的是线程锁状态状态还有hashcode
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值