锁机制

1.按照实现方式

按照实现方式分为乐观锁和悲观锁,并不是真实存在的锁,而是设计思想

  • 乐观锁
    认为资源和数据不会被别人修改,读取不会上锁,但是写入操作会判断是否被修改过
    使用场景:
    高性能、高可用、高并发的场景,适用于写操作比较少的场景,冲突比较少
    实现方案:

    • 版本号机制
      版本号机制是在数据表中加上一个 version 字段来实现的,表示数据被修改的次数,当执行写操作并且写入成功后,version = version + 1,当线程A要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功
    • CAS实现
      CAS(Compare And Swap),不使用锁实现变量同步,也叫非阻塞同步
      三个元素
      1. 需要读写的内存值V
      2. 进行比较的值A
      3. 拟写入的新值B
        当且仅当A与V值相同时,将内存值V修改为B,否则什么也不做

    Java中的java.util.concurrect.atomic下的原子变量就是通过CAS实现的,JDK1.5中JUC(java.util.concurrect)包建立在CAS基础上

    • 乐观锁的缺点
      • ABA问题
        如果一个变量第一次读取为A,第二次读取也为A,并不代表没被修改过,有可能是A->B->A,JDK1.5之后AtomicStampedReference类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。DCAS就是在CAS基础上增加引用的表示修改次数的标记符。
      • 循环开销大
        乐观锁在进行写操作判断是否能写入成功,如果不成功会触发等待->重试机制,是一种自旋锁,适用于短期获取不到锁的情况,若长期获取不到,性能开销较大,如果锁竞争严重,CAS自旋的概率较大,此时应该有效考虑Synchronized
  • 悲观锁
    一种悲观思想,认为数据可能会被其他人修改,所以在持有数据时把数据或资源锁住,其他线程请求资源会阻塞,直到悲观锁释放,数据库中的行锁,表锁,读锁,写锁都是在操作之前上锁
    Java中SynchronizationReetranLock等排他锁(独占锁)也是悲观锁
    使用场景:
    因为对读写都要枷锁,性能较低,高安全性的场景,适用于写操作比较多的场景,冲突比较多

2. 按照冲突的处理方式

  • 自旋锁
    定义:当一个线程尝试获取一个锁,若锁被占用,则职该线程将会等待,间隔一段时间再次尝试获取,进入循环->等待机制。
    优势:如果持有锁的线程可以在短时间释放,那么等待竞争的线程不需要做内核态/用户态的切换进入阻塞态,他们只需要等待持有锁的线程释放,避免了用户态内核态切换的消耗(避免了进程调度和线程切换),所以操作系统大量使用自旋锁。
    劣势:如果长时间上锁,自旋锁非常消耗性能,则指了其他线程的调度运行。线程持有锁的时间越长,则该线程被OS调度程序中断的风险越大。如果发生中断,其他线程将保持自旋,而持有锁的线程并不打算释放锁,导致无限推迟,直到该线程完成工作。解决该问题可以给自旋锁是指自选时间,时间到了就释放自旋锁,自旋锁会占用CPU资源,影响系统性能。

  • 互斥锁
    定义:当一个线程获取一个锁,若锁被占用,则该线程进入阻塞态,等待重新调度
    自旋锁不适用于线程长时间持有CPU资源的情况,会加剧系统负担。但互斥锁在加锁失败后进入阻塞态,此时需要进行线程上下文切换,消耗系统资源。

  • 原子操作
    原子操作:不能中断的操作,在此操作过程中,CPU不会再去其他的针对该值的操作,通过CPU指令实现,其它的同步技术常常依赖于原子操作

posted @ 2022-05-02 11:27  流光之中  阅读(51)  评论(0编辑  收藏  举报