并发编程:各种锁机制、锁区别、并发工具类深刻总结

博主总结

1、各种锁全面快速了解

  • 悲观锁(排他锁、互斥锁),上下文切换频繁。适合并发量高、对读写资源竞争高的场景
  • 乐观锁(自旋锁,循环实现CAS机制),读多写少且竞争小的。轻量,避免锁竞争。因此在读取数据时不加锁,而是在更新数据时通过CAS机制来验证数据是否被其他线程修改过。ABA问题是在使用CAS时可能出现的一种并发问题。在多线程环境下,如果一个变量的值先被线程A修改为B,然后又被线程B修改回A,那么在使用CAS进行比较和交换操作时,尽管变量的当前值与预期值相同(都是A),但实际上这个变量的值已经被修改过,这就是ABA问题。
  • 公平锁(先来后到)、非公平锁(本线程优先获取锁, synchornized)
  • 排他锁和共享锁,可以看作写锁(会造成线程安全问题)和读锁(不会造成线程安全问题,会共享起来提升读的性能,可结合Volatile保证可见性)
  • 可重入锁(递归锁),可重入锁其实就是同一个线程持有的锁,synchronized和ReentranLock具有可重入锁特性,可重复持有的锁
  • 锁升级:无锁 -> 偏向锁(可看作可重入锁) -> 轻量级锁 (可看作乐观锁)-> 重量级锁(悲观锁(自旋锁),涉及到从应用态切换到内核态),它们是synchornized底层所升级的四种状态,synchornized随着并发量的增加锁的重量就会增加,锁越重性能越低但就越安全。

2、悲观锁和乐观锁详解

  • 悲观锁与乐观锁是什么?

    • 悲观锁:共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    • 乐观锁:无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。在 Java 中java.util.concurrent.atomic包下面的原子变量类(比如AtomicInteger、LongAdder)就是使用了乐观锁的一种实现方式 CAS 实现的。
      • LongAdder:它在内部维护了多个计数器(通常使用数组),每个线程可以独立地对自己的计数器进行更新,从而减少竞态条件。只有在需要计算总和时,这些计数器的值才会被汇总。因此,它在高并发场景下能够提供更好的性能。
  • 悲观锁和乐观锁如何选用?

    • 悲观锁通常多用于写比较多的情况(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如LongAdder),也是可以考虑使用乐观锁的,要视实际情况而定。
    • 乐观锁通常多用于写比较少的情况(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考java.util.concurrent.atomic包下面的原子变量类)。
  • 乐观锁的实现(CAS和版本号法,CAS相对使用较多)

    • CAS,下面详解吧!
    • 版本号法:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

3、synchronized和volatile区别?

  • synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

4、synchronized和reentrantLock区别?

  • 二者都是可重入锁。
  • synchronized依赖于JVM,而RentrantLock依赖于JDK的API实现。
  • ReentrantLock相较于sync提供很多功能增强。

    • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说当前线程在等待获取锁的过程中,如果其他线程中断当前线程「 interrupt() 」,当前线程就会抛出 InterruptedException 异常,可以捕捉该异常进行相应处理。
    • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来指定是否是公平的。
    • 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。
    • 支持超时 :ReentrantLock 提供了 tryLock(timeout) 的方法,可以指定等待获取锁的最长等待时间,如果超过了等待时间,就会获取锁失败,不会一直等待。

5、公平锁和非公平锁区别?

  • 公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁。
  • 非公平锁:锁被释放之后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁。

6、可中断锁和不可中断锁区别?

  • 可中断锁:获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。ReentrantLock 就属于是可中断锁。
  • 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其他的逻辑处理。 synchronized 就属于是不可中断锁。

7、共享锁和独占锁区别?

  • 共享锁:一把锁可以被多个线程同时获得。
  • 独占锁:一把锁只能被一个线程获得。

8、并发工具类

  • CountDownLatch 是 JUC 中的一个同步工具类,用于协调多个线程之间的同步,确保主线程在多个子线程完成任务后继续执行。
  • CyclicBarrier 的字面意思是可循环使用的屏障,用于多个线程相互等待,直到所有线程都到达屏障后再同时执行。
  • Semaphore------信号量,用于控制同时访问某个资源的线程数量,类似限流器,确保最多只有指定数量的线程能够访问某个资源,超过的必须等待。
  • Exchanger------交换者,用于在两个线程之间进行数据交换。
  • CyclicBarrier和CountDownLatch区别?
相关推荐
高铭杰18 天前
Postgresql源码(144)LockRelease常规锁释放流程分析
数据库·postgresql··lockrelease·regularlock
python_chai1 个月前
Python多进程并发编程:深入理解Lock与Semaphore的实战应用与避坑指南
开发语言·python·高并发·多进程··信号量
小徐Chao努力1 个月前
【解析】ReentrantLock锁、Syschronized锁面试点解析
java·面试·职场和发展·synchronized·
码熔burning1 个月前
ReentrantLock 实现公平锁和非公平锁的原理!
多线程··reentrantlock
忘忧人生1 个月前
Redisson 实现分布式锁
分布式锁·redisson·
小小工匠2 个月前
MySQL - 事务隔离级别和锁的机制
mysql··事务隔离级别
Golinie2 个月前
【Go万字洗髓经】Golang中sync.Mutex的单机锁:实现原理与底层源码
golang·并发·mutex··sync.mutex
秋‍.2 个月前
Java锁等待唤醒机制
java·开发语言··等待唤醒机制
fananchong22 个月前
MySQL InnoDB 事务隔离级别和锁
sql·mysql·innodb··事务隔离级别