【线程】Java并发编程——锁机制

发布于:2024-12-06 ⋅ 阅读:(144) ⋅ 点赞:(0)

【线程】Java并发编程进阶与扩展

      • 一、锁机制
        • 1.1锁策略
        • 1.2 synchronized内部工作原理

一、锁机制

1.1锁策略

加锁后,在处理冲突的过程中,涉及到一些不同的处理方式。此处的锁策略,并非是Java独有的。任何与“锁”相关的话题,都会涉及到以下内容。

  1. 乐观锁与悲观锁
  • 乐观锁:假设数据⼀般情况下不会产⽣并发冲突,所以在数据进⾏提交更新的时候,才会正式对数据是否产⽣
    并发冲突进⾏检测。
  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别⼈会修改,所以每次在拿数据的时候都会上锁,
    这样别⼈想拿这个数据就会阻塞直到它拿到锁。
  1. 轻量级锁与重量级锁
  • 轻量级锁:加锁的开销比较小,加锁快。轻量级锁一般都是乐观锁。
  • 重量级锁:加锁的开销比较大,加锁速度慢。重量级锁一般都是悲观锁。
  1. 自旋锁与挂起等待锁
  • 自旋锁:在进行加锁的时候,搭配一个while循环。如果加锁成功,循环结束;如果加锁失败,不是阻塞放弃CPU(缺点:浪费CPU资源,优点:锁被释放后,能够第一时间拿到锁),而是进行下一次循环,再次尝试获取锁。是典型的乐观锁、轻量级锁。
  • 挂起等待锁:表示当获取锁失败之后,对应的线程就要在内核中挂起等待(放弃CPU,进入等待队列),需要在锁被释放之后由操作系统唤醒(不能第一时间获取到锁,什么时候加锁由系统决定)。是典型的悲观锁、重量级锁。

synchronized是哪一种锁?
synchronized具有自适应能力,即某些情况下是乐观锁/轻量级锁/自旋锁,某些情况下是悲观锁/重量级锁/挂起等待锁。
如果当前锁冲突的激烈程度不大,就处于乐观锁/轻量级锁/自旋锁;
如果当前锁冲突的激烈程度很大,就处于悲观锁/重量级锁/挂起等待锁。

4.互斥锁与读写锁

  • 互斥锁:像synchronized这样的锁,只是单纯的加锁解锁,不区分读锁和写锁。
  • 读写锁:需要分别加读锁和写锁,读锁之间并不互斥,读写、写写之间互斥。

在Java里读写锁是通过ReentrantReadWriteLock这个类来实现。

// 创建一个读写锁
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获取读锁
rwLock.readLock().lock();
// 释放读锁
rwLock.readLock().unlock();
// 创建一个写锁
rwLock.writeLock().lock();
// 写锁 释放
rwLock.writeLock().unlock();
  1. 公平锁与非公平锁
  • 公平锁:遵守先来后到的锁称为公平锁。优点在于不会出现“线程饥饿”问题。
  • 非公平锁:多个线程获取锁的顺序有一定的随机性,并非按照先到先得的方式进行(随机获取)。优点在于性能相对较高。
  1. 可重入锁与不可重入锁
  • 可重入锁:一个线程可以针对一把锁,连续加锁多次,不会死锁,就是可重入锁。
  • 不可重入锁:一个线程加锁多次会出现死锁,就是不可重入锁。

参考内容:多线程(八):常见锁策略

像synchronized这样的锁,是一个乐观锁/悲观锁自适应、轻量级锁/重量级锁自适应、自旋锁/挂起等待锁自适应、互斥锁、非公平锁、可重入锁。
像Linux系统原生的mutex锁,是一个悲观锁、重量级锁、挂起等待锁、互斥锁、非公平锁、不可重入锁。

1.2 synchronized内部工作原理
  • 锁升级
    这部分我们主要是讲清楚何为自适应
    当线程执行到synchronized的时候,如果这个对象当前处于未被加锁的状态,就会经历以下过程:

    1. 偏向锁阶段
      这个阶段并非真正加锁了,而是做了一个非常轻量的标记。
    2. 轻量级锁阶段
      假设存在锁竞争,但是不多。此处就实现一个自旋锁。当自旋的锁变多了(锁竞争变得更加激烈了,大量的自旋锁消耗了大量的CPU资源),就会进一步升级到重量级锁。
    3. 重量级锁阶段
      此时拿不到锁的线程就不会继续自旋了,而是进入“阻塞等待”(挂起等待锁),让出CPU(不会消耗很多的CPU资源)。当锁被释放后,由系统随机唤醒一个线程来获取锁。

    需要注意的是,此处的锁只能单向升级,不能降级。
    举个例子:
    此处这个过程类似于处对象第一阶段就是搞暧昧,没有确定关系(只是进行标记,没有加锁),此时的开销就比较小。但是如果有别的妹子靠近我家鸽鸽,此时就要进入第二阶段,马上跟我家鸽鸽确定关系,让其他妹子离我家鸽鸽远点(轻量级锁,此时就存在锁竞争了)。当追求我家鸽鸽的人比较少的时候,她们都认为我跟我家鸽鸽分手后,她们就有很大的机会跟我家鸽鸽在一起,此时她们就一直围在我家鸽鸽身边,我一分手,他们就能立马跟我家鸽鸽确认关系(此时仍然是轻量级锁阶段),但是当追求我家鸽鸽的人比较多的时候,她们就认为追求到我家鸽鸽的机会不大,就都散去了。此时就进入第三阶段,当我跟我家鸽鸽分手后,隔一段时间后,有个妹子知道我跟我家鸽鸽分手了,就来跟我家鸽鸽确认关系,但是这个过程中,不知道隔了多久(不像第二阶段,我一分手,她们就立马能来和我家鸽鸽确认关系)。

  • 锁消除
    是synchronized中内置的优化策略。是编译器优化的一种方式,如果编译器编译代码的时候,发现这个代码,不需要加锁,就会自动把这个锁给消除掉。
    需要注意的是,这里的优化是比较保守的。比如单线程加锁、加锁代码中没有修改成员变量,只修改了一些局部变量的情况。很多的这种“模棱两可”,编译器也不能确定是否要加锁的代码,都不会进行锁消除。
    锁消除,针对的是那些一眼看上去就完全不涉及线程安全问题的代码,将锁消除掉。

  • 锁粗化
    将多个细粒度的锁,合并成一个粗粒度的锁。
    所谓的细粒度,就是被加锁的代码量比较少,是一个相对的概念。
    在这里插入图片描述

小结:synchronized背后涉及的“优化手段”?

  1. 锁升级,偏向锁->轻量级锁->重量级锁
  2. 锁消除,自动消除掉不必要的锁
  3. 锁粗化,把多个细粒度的锁合并成一个粗粒度的锁,较少锁竞争的开销。

网站公告

今日签到

点亮在社区的每一天
去签到