• 多线程中的各种锁


    注意
    博主是初学者,此文包含个人理解,谨慎阅读

    乐观锁与悲观锁

    悲观锁
    总是认为临界资源会被多个线程同时争用,于是在使用之前,先对资源加锁,使其它线程阻塞,使用完成之后再释放资源
    乐观锁
    认为临界资源大多数时间不会被多个线程同时争用,在进行修改操作时,通过某些手段,检测有没有其他线程使用了此共享资源,如果没有,操作成功,如果有,拒绝访问,并重试.

    在硬件上,有专门处理器指令来处理这一过程
    1.测试并设置(Test-and-Set)
    2.获取并增加(Fetch-and-Increment)
    3.交换(Swap)
    4.比较并交换(Compare-and-Swap) 这就是CAS
    5.加载链接/条件储存 (Load-Linked/Stroe-Conditional)

    MySQL数据库中,MVCC使用了基于版本号机制的乐观锁

    公平锁与非公平锁

    公平锁
    对于等待统一临界资源的线程来说,资源被释放后,先到先得
    非公平锁
    无法保证下一个是谁获取临界资源

    可重入锁与不可重入锁

    可重入锁
    一条线程能够反复进入被它只有锁的同步块.
    使用一个信号值标志该临界资源有没有被占用,每一次进入时是信号值+1,释放资源时-1,信号值为0则标志当前临界资源可用

    不可重入锁
    如果一个线程想要再次访问已经被自己占用的临界资源,可能死锁

    轻量级,重量级以及偏向锁

    重量级锁
    在java中,synchronize关键字表现出的语义和重量级锁相同,到来即占用,离开即释放

    在对象头中的Mark Word中,如果没有上锁,那么需要存储对象hash码,分代年龄等信息.
    无论有没有上锁,Mark Word的最后两位都是标志位,其中,01表示没有上锁或者是偏向锁

    如果标志位是10,即表示当前对象被重量级锁了.
    此时,对象的hash码等信息不再存储于Mark Word中,代表重量级锁的ObjectMonitor类中,会保存原来Mark Word中的信息,原来的Mark Word存储hash码等信息的bit,用于表示指向重量级锁的指针
    注意,在这些变化中,Mark Word的最后两个字节始终表示标志位.

    重量级锁的实现方式用到了互斥
    对于synchronize关键字来说,JAVAC在编译时,会产生monitorenter和monitorexit两条字节码指令,分别表示访问临界资源和释放临界资源.
    执行monitorenter时,会使信号量(锁计数器)加一;执行monitorexit时,会使信号量(锁计数器)减一,当信号量为0时,就表示这临界资源当前没有被锁定
    由此可见,重量级锁是可重入锁.

    在获取锁时,如果失败了,那么当前线程应该被阻塞,由此可见,重量级锁是悲观锁.

    然而,synchronize关键字在JVM中被执行时,并非就一定是重量级锁,实际上,轻量级锁和偏向锁是对其的一种优化

    轻量级锁
    轻量级锁的标志位是00,它的实现与重量级锁类似,将Mark Word中的部分bit化为指针,指向Lock Record
    Lock Record是轻量级锁为Mark Word提供的一个拷贝,其中包含了原来存储在Mark Word的相关信息.

    与重量级锁不同的是,轻量级锁并没有使用互斥来实现,而是使用了CAS
    这就意味着,轻量级锁实际上是一种乐观锁吗?至少我现在认为,不应该说轻量级锁是乐观锁.因为它也像重量级锁一下,锁住了临界资源,只是,给临界资源上锁时,使用了乐观锁的方式
    轻量级锁会尝试使用CAS把对象的Mark Word指向Lock Record,并更新标志位.
    如果失败了,当前线程会试图使用自旋(这个稍后说)来获取锁,如果仍然失败,那么此时,轻量级锁就必须膨胀为重量级锁,以减少自旋带来的消耗

    轻量级锁比重量级锁性能优化体现在,将互斥转化为了CAS,但是如果在存在多线程争用临界资源的情况下,轻量级锁的消耗要高于重量级锁.

    关于解锁,轻量级锁也使用了CAS

    JVM在获取轻量级锁是,如果失败了,会检查对象的Mark Word是否指向了当前线程的栈帧,如果是,直接执行代码.
    由此可见,轻量级锁也是可重入锁

    偏向锁
    偏向指的是对线程的偏向,如果一个线程获取了锁,那么在没有其他线程争用的情况下,这个锁就一直归这个线程所有了,不再需要解锁等操作.

    偏向锁的标志位是01,和无锁相同,所以需要倒数第三位表示偏向模式,倒数第三位为0表示无锁,倒数第三位为1表示偏向锁

    偏向锁的加锁操作也是通过CAS来完成的,比起轻量级锁,它避免了在单个线程中多次使用CAS来锁住统一个对象.
    但是一旦有第二个线程争用临界资源,偏向模式立刻结束.

    值得注意的是,偏向模式直接修改了Mark Word而没有留下备份,而在java中,JVM需要保证,没有重写hashcode方法的对象,其hash只能被计算一次.
    所以,对于没有重写hashcode方法的对象,如果以及计算过hash值,那么它就无法被偏向锁锁定.如果处于偏向锁状态,计算hash值也会直接导致偏向模式的退出

    这是《深入理解java虚拟机(第三版)》P482的图
    表示的是,32位Mark Word的各种状态

    JVM中的其他锁优化

    这些优化,并不一定在所有情况下都会减少锁的消耗

    自旋锁
    如果发现临界资源已经被其他线程占用,先不进入阻塞状态,而是在CPU上执行一个忙循环(自旋),看看能不能等到临界资源被释放
    如果在规定时间没有等到,那就老实去阻塞.
    自适应的自旋锁就是,根据一些变量,比如自旋成功次数等,动态决定自旋应该等待的时间

    锁消除
    去掉没有必要加的锁

    锁粗化
    增大锁的颗粒
    一般来说,锁颗粒越小越好,有利于其他线程获取资源,避免过多的阻塞.
    但是如果在一个线程中,反复加锁解锁,对性能也有较大影响.

    其它

    不要使用拥有不可变特性的对象,作为锁的对象,还在临界区修改该对象

  • 相关阅读:
    Windows-快速预览文件-QuickLook
    Chrome简洁高效管理下载项
    有Bug?你的代码神兽选对了吗
    保护视力-刻不容缓
    一次看懂 Https 证书认证
    Web前端助手-功能丰富的Chrome插件
    Chrome自动格式化Json输出
    网络爬虫
    彻底搞懂Cookie,Session,Token三者的区别
    Redis内存满了的解决办法
  • 原文地址:https://www.cnblogs.com/ZGQblogs/p/12629912.html
Copyright © 2020-2023  润新知