Java并发编程的艺术笔记(二)

2. Java并发机制的底层实现原理

1. volatile

​ volatile相当于轻量级的synchronized,作用是保证数据的可见性,简单来说就是保证数据的一致性(使其他线程所缓存的volatile修饰字段的值失效,因此也会浪费其他线程的一些时间)。如果使用得当,会比synchronized的执行成本更低。

volatile修饰的变量就是告知系统,任何对此变量的操作都需要从共享内存中获取,而对他的改变则必须刷新回共享内存中,保证所有线程对共享变量访问的可见性。

1. volatile的两个实现原则
  1. Lock前缀指令会引起处理器缓存回写到内存,保证缓存的一致性。
  2. 一个处理器的缓存回写到内存中,会导致其他处理器的内存无效。
2. volatile的优化

最佳64字节能够提高并发编程的效率。因为处理器的高速缓存行为64字节宽,进行补位,可以提高效率。

但是扩展64字节的方法并不是所有时候都要使用:

  • 缓存行不是64字节宽的处理器不要使用
  • 共享数据写次数较少

追加到64字节是需要浪费时间和空间的,处理器读取时也会带来性能的开销。

2. synchronized

synchronized在1.6以前是重量级锁的所在,在1.6之后为了减少获得锁和释放锁带来的开销,增加了偏向锁和轻量级锁。

Java中每一个对象都可以作为锁,具体表现是:

  • 普通同步方法是锁当前对象。
  • 静态同步方法是锁当前类对象。
  • 同步方法块是锁括号内的对象。

synchronized实现与monitor指令有关,monitorenter指令在编译后插入到同步代码块的开始位置,monitorexit指令则插入到方法结束出或者异常处。synchronized用的锁存在于对象头中,数组类型3个字宽,非数组类型2个字宽(1 Word = 4 Byte , 1 Byte = 8 bit)

1. Java对象头
长度内容说明
32bit/64bitMark Word存储对象的HashCode、分代年龄、锁标记位
32bit/64bitClass MetaData Address存储到对象类型数据的指针
32bit/32bitArray Length数组的长度(如果当前对象是数组)
2. 锁升级和对比

锁四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。

锁只能升级但不能降级,目的是为了提高获得锁和释放锁的效率。

1. 偏向锁

偏向锁存在于Java对象头和方法的栈帧中,一个线程访问当前的同步代码块获得锁时,会在对象头和栈帧中记录此线程的ID,这就是偏向锁。以后此线程访问和退出时,都无需进行CAS操作来加锁和解锁,只需要检测一下Mark Word中的偏向锁是否偏向此线程。如果测试成功,则不用获得锁;如果测试失败,此线程还需要测试Mark Word中的偏向锁标识是否为1,如果为1,则尝试用CAS将对象头的偏向锁指向当前线程,否则通过CAS竞争锁。

偏向锁撤销:

如果当前有线程1访问此对象,那么当线程2要访问时导致线程1挂起,然后两个线程竞争锁。只有竞争时才会释放偏向锁,偏向锁撤销时需要该线程不处于活动状态。所以有竞争时先将线程挂起,将栈中的锁记录和对象头中的Mark Word重新偏向或者恢复到无锁状态。

偏向锁默认开启,但是激活慢。

  • 关闭偏向锁的延迟:-XX:BiasedLockingStartupDelay=0。

  • 关闭偏向锁:-XX:UseBiasedLocking=false。会默认进入轻量级锁状态。

2. 轻量级锁

线程访问同步代码块之前,JVM会先在栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制过去。然后尝试使用CAS将对象头中的Mark Word替换成指向锁记录的指针。失败了代表要竞争此资源,当前线程会自旋来获取锁。

轻量级锁出现在少量线程访问同步代码块并且执行时间比较短,没必要使用重量级锁,自旋获取锁会带来CPU的额外开销。

轻量级锁解锁:

轻量级锁解锁时,会将之前复制过去的对象头替换回此对象头的Mark Word。如果成功,表示没有竞争;失败了锁就会变成重量级锁。

3. 锁对比
优点缺点适用于的场景
偏向锁加锁和解锁开销比较小,执行速度相当于无锁级别的当线程多时,会因为偏向锁带来额外的开销线程几乎只有一个
轻量级锁减少了上下文切换所带来的开销,竞争的线程不会堵塞,减低了线程的响应时间自旋获取锁给CPU带来了压力,增加了CPU的开销线程少量,并且同步代码块执行时间短
重量级锁线程不会自旋,减少了CPU的消耗,保证了线程的执行安全线程堵塞,导致了线程响应时间过长追求吞吐量,同步代码块执行时间较长
4. CAS操作

compareAndSwap的缩写,CAS属于乐观锁,乐观锁是假定冲突不会发生,如果发生冲突了,那就重试直到成功。synchronized、ReentrantLock、Atomic开头的原子类,底层都用到了CAS。CAS底层使用的是JNI调用C代码实现的,CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新。

比较且替换指的是当前状态值和预估值相同,则以原子方式将同步状态设置为给定的更新值。

  • ABA问题:上面说到CAS会检查值的变化情况,这是就会出现A->B->A问题,CAS检测的是没有变,所以要加版本号A1->B2->A3。
  • 循环时间:上文也说到,如果冲突,就会自旋,会增加CPU的开销。

3. Lock

1. ReentrantLock

Java中两个独占锁,ReentrantLock和synchronized,虽然性能上没有太大的区别,但是ReentrantLock的功能更加强大。

ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。ReentrantLock的实现依赖于AbstractQueueSynchronizer(AQS)。

公平锁加锁:

  1. ReentrantLock:lock() ;
  2. FairSync:lock() ;
  3. AbstractQueueSynchronizer:acquire(int arg) ;
  4. ReentrantLock:try Acquire(int acquires) ;

解锁:

  1. ReentrantLock:unlock() ;
  2. AbstractQueueSynchronizer:release(int arg)
  3. Sync:tryRelease(int releases) ;

4. ReentrantLock和Synchronized对比

ReentrantLockSynchronized
锁实现机制依赖AQS监视器模式
灵活性支持响应中断、超时、尝试获取锁不灵活
释放形式显示调用unlock()释放锁自动释放监视器
锁类型公平锁和非公平锁非公平锁
条件队列可关联多个条件队列只关联一个同步队列
可重入性可重入可重入

5. 原子性操作实现原理*

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值