同步锁的分类

在这里插入图片描述

这些分类是从不同的角度出发去看的,并不互斥,也就是多个类型可以并存。比如ReentrantLock既是互斥锁又是可重入锁。

乐观锁和悲观锁

  • 互斥同步锁的劣势

    • 阻塞和唤醒带来的性能劣势:比如操作系统用户态和内核态的切换,上下文的切换。而乐观锁不需要把线程挂起,自然就没有这些影响。
    • 永久阻塞:如果持有锁的线程被永久阻塞,那么等待该线程释放锁的那几个线程将永远也得不到执行。
    • 优先级反转
  • 什么是乐观锁和悲观锁

    • 悲观锁:它认为如果不锁住资源,其他线程就会来抢占,就会造成数据错误,所以为了保证结果正确性,会在每次获取修改数据时,把数据锁住,让别人无法访问该数据。如synchronized和lock接口
    • 乐观锁:它认为自己在处理操作的时候不会有其他线程来干扰,所以不会锁住被操作对象,在更新的时候会判断自己修改数据的时候,有没有被其他线程修改过,没有就正常修改;如果数据和开始拿到的不一样了,就说明有其他线程修改过,这时可以选择放弃、报错、重试等策略来应对。乐观锁一般都是采用CAS算法来实现的。
  • 典型例子

    • 悲观锁:synchronized和lock
    • 乐观锁:原子类和并发容器
  • 开销对比

    • 悲观锁的原始开销要高于乐观锁,但特点是一劳永逸,临界区持锁时间就算越来越差,也不会对互斥锁的开销造成影响。
    • 虽然乐观锁一开始的开销比悲观锁小,但是如果自旋时间很长或者不停重试,那么消耗的资源也会越来越多。
  • 使用场景

    • 悲观锁:适合并发写入多的情况,适用于临界区持锁时间比较长的情况,悲观锁可以避免大量的无用自旋等消耗
      • 临界区有IO操作
      • 临界区代码复杂或者循环量大
      • 临界区竞争非常激烈
    • 乐观锁:适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅提高

可重入锁和非可重入锁,已ReentrantLock为例(重点)

  • 可重入性质
    • 什么是可重入
      • 同一个线程可以多次获取同一把锁
    • 好处
      • 避免死锁
      • 提升封装性
  • 一些方法介绍
    • getHoldCount:获得当前已经被重入的数量
    • isHeldByCurrentThread:判断锁是否被当前线程持有
    • getQueueLength:返回当前正在等待这把锁的队列有多长

公平锁和非公平锁

默认是非公平的。

  • 什么是公平和非公平
    • 公平是指按照线程请求的顺序来分配锁;非公平是指不完全按照请求的顺序,在一定的情况下,可以插队。
  • 为什么要有非公平
    • 避免唤醒带来的空档期,提高效率
  • 特例
    • tryLock()不遵守设定的规则。当有线程释放了锁,那么这个正在tryLock的线程就能获取到锁,即使在它之前已经有其他线程在等待队列里了。
  • 对比公平和非公平的优缺点

共享锁和排它锁

  • 排它锁,又称为独占锁、独享锁

  • 共享锁,又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其他线程此时也可以获取到共享锁,也可以查看但无法修改和删除数据

  • 共享锁和排它锁的典型是读写锁ReentrantReadWriteLock,其中读锁是共享锁,写锁是独享锁。这里我们用ReentrantReadWriteLock为例。

  • 读写锁的作用

    • 不使用读写锁的话,虽然可以保证线程安全,但是也浪费了一定的资源:多个读操作同时进行并没有线程安全问题,不需要锁住。
    • 读写锁的原理:在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,提高了程序的执行效率
  • 读写的规则

    • 多个线程只申请读锁,都可以申请到; 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁; 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
    • 一句话总结:要么多读,要么一写
    • 另一种思路:读写锁只是一把锁,可以通过两种方式锁定:读锁定和写锁定。读写锁可以被一个或者多个线程读锁定,也可以被单一线程写锁定。但是不能同时对这把锁进行读锁定和写锁定。
  • 读锁和写锁的交互方式

    • 读锁插队策略(默认非公平,公平不能插队)
      • 写锁可以随时插队,但如果写锁已经被获取时不可以插队。
      • 读锁仅在等待队列头结点不是想获取写锁的线程的时候可以插队。
    • 锁的升降级
      • 如果持有写锁,想要进行读操作。可以直接把锁降级到读锁,提高转换效率
      • 支持锁降级,不支持锁升级。(若支持升级,会有多个读锁均想获得写锁,造成死锁)

自旋锁和阻塞锁

  • 概念
    • 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间
    • 如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长,为了这一小段时间去切换线程,线程挂起和恢复线程的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让当前请求线程不放弃CPU执行时间,进行自旋,如果在自旋完成后,锁定的同步资源已经释放了锁,那当前线程就不需要阻塞而是直接获取同步资源,避免切换线程开销,这就是自旋锁。阻塞锁就是没拿到锁就阻塞,直到被唤醒。
  • 缺点
    • 如果锁被占用时间很长,那么自旋只会浪费处理器资源。
  • 原理
    • 自旋锁的实现原理是CAS
  • 适用场景
    • 自旋锁一般用于多核服务器,在并发度不是很高的情况下,比阻塞锁效率高
    • 自旋锁适用于临界区比较短小的情况。

可中断锁

  • synchronized是不可中断锁,Lock是可中断锁,因为tryLock和lockInterruptibly都可以响应中断。
  • 线程A获取锁,B正在等待该锁,此时B不想等待了,可以中断等待,去执行其他任务。,这就是可中断锁。

锁的优化

  • JVM虚拟机对锁的优化

    • 自旋锁和自适应
    • 锁消除
      • 访问的数据是方法内部的,不会有外部线程访问到,就可以不需要加锁
    • 锁粗化
      • 一连串的加锁减锁都是对一个对象的,就把锁定范围扩大,减少加锁减锁的次数。
  • 代码优化

    • 缩小同步代码块
    • 尽量不要锁住方法
    • 减少请求锁的次数
    • 避免认为制造“热点”,即故意多次访问共享数据。
    • 锁中尽量不要再包含锁
    • 选择合适的工具类
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值