JUC
乐观锁
原理
是一种乐观思想,认为目标数据读多写少,默认不加锁,数据会维护一个版本号
读取数据:线程读取数据时不加锁,
写入数据:写入时,先读取数据以及版本号,然后写入修改后的数据,以及版本号,这时使用版本号进行CAS操作(一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败),失败则重试
应用
数据库乐观锁:数据表增加一个版本号字段来实现乐观锁。当更新数据时,同时检查版本号或时间戳
Java对象乐观锁:java.util.concurrent.atomic 包;
悲观锁
原理
是一种悲观思想,目标数据会被抢夺,默认加锁,只有一个线程可以读取/写入数据
读取数据:先尝试加锁,然后读取数据
写入数据:想尝试加锁,然后写入数据
应用
java对象:synchronized,ReentrantLock
自旋锁
原理
是一种偏乐观思想,认为持有目标数据的锁的线程可以很快执行完毕,持有CPU资源的情况下,通过CAS持续尝试获取目标数据的锁,
优点
避免线程之间的缺陷
缺点
应用
java自旋锁:自旋锁的自旋时间时需要设置的,在 java.util.concurrent.locks 包中,可以使用 ReentrantLock 类来实现自旋锁的逻辑,ReentrantLock 默认情况下是非公平锁,可以通过构造函数传入 fair = true 来创建公平锁。
java.util.concurrent.atomic 包中的一些原子类如 AtomicInteger等,使用了 CAS操作来实现无锁的并发控制。虽然它们不是传统的自旋锁,但在实现上使用了自旋的思想
锁升级
原理
锁升级:对悲观锁的一种优化方案,升级过程:偏向锁->轻量级锁->重量级锁
偏向锁:直接获取锁:单线程获取目标数据,处于偏向锁状态,可重入,无需重复获取锁。单线程操作完后释放锁
轻量级锁:CAS获取锁:又叫可重入锁,多线程获取目标数据锁,会升级成轻量级锁,多线程通过CAS竞争轻量级 锁时,失败会陷入自旋或者升级为重量级锁
重量级锁:切换到内核态获取锁:多线程获取目标数据的锁,某个线程自旋到一定时间/次数后会升级为重量级 锁,不会自动降级。由于重量级锁使用操作系统的互斥量(Mutex)实现,所以使用重量级锁会导致 用户态->内核态之间的切换,也会导致线程上下文切换,效率较低
优点
- 适合在目标数据并发较低的时候使用(使用锁升级优化后的)synchronized
- 目标数据被少量线程频繁访问,偏向锁会减少不必要的同步
缺点
并发较高的时候会导致升级到重量级锁,效率较低,由于使用重量级锁的底层原理是调用操作系统的API,会导致切换到内核态,同时会切换县城上下文;锁的持有者释放了锁,操作系统会将等待锁的线程唤醒,并将其切换回用户态。这次有什么问题吗
应用
适合目标数据并发较低和被少量线程频繁访问的场景
synchronized与ReentrantLock对比
原理
synchronized:
基于 Java 虚拟机(JVM)内置监视器机制实现的,JVM 使用对象的监视器(也称为内部锁)来实现 synchronized 块。线程进入 synchronized 块时,会获取与该对象关联的监视器。监视器是一个内部数据结构,包含了锁和等待队列。如监视器没被其他线程持有,当前线程会获取锁并执行临界区代码。如另一个线程已经持有监视器,当前线程会被阻塞放入等待队列,等待监视器释放。当持有监视器线程释放锁,等待队列中的某个线程会被唤醒并执行。
- 非公平锁:不需要手动加锁解锁,通过对象头的标志位实现锁的获取和释放
- 可重入锁:可多次获得锁不会被阻塞,每次获锁成功,锁计数器加一,解锁减一,直到计数器为零会释放锁
ReentrantLock:
使用显式的锁定和解锁操作,通过 lock() 和 unlock() 方法进行操作。底层实现上,ReentrantLock 使用 CAS(Compare and Swap)操作来实现锁。 ReentrantLock 提供了更多的灵活性和功能,如可中断锁和公平锁
- 可实现公平和非公平锁,
java.util.concurrent.locks中的类,基于 AQS(AbstractQueuedSynchronizer)框架实现可重入锁,通过CAS实现锁的获取和释放,需要手动操作。 - 可重入锁,用
lock()方法获取锁,要在后续代码中用unlock()释放锁 - 底层使用 CAS 来尝试获取锁,成功则获取锁,失败则使用自旋等待
优点
synchronized:
- 不需要手动加锁解锁
- 自动支持可重入
ReentrantLock:
- 支持尝试获取锁,可以选择等待一段时间尝试获取锁。
- 支持公平锁和非公平锁,可以通过构造函数指定。
缺点
synchronized:
- 不支持尝试获取锁,会一直等待直到获取锁成功或线程被中断。
- 自动支持可重入
- 作用于方法锁的是对象实例:当一个线程访问到被
synchronized锁住的方法时,其他线程无法访问该对象的其他方法或变量 - 无法只锁方法中的部分代码,颗粒度大
- 当
synchronized作用在静态方法/Object.class上时,锁住的是该类的Class对象,因此其他线程无法同时访问该类其他被synchronized修饰的静态方法。但其他线程仍可访问该类非synchronized修饰的静态方法 - 当
synchronized作用在实例方法/实例上时,锁住的是该实例对象(即this)。当一个线程获取该实例方法或代码块的锁时,其他线程无法同时访问该实例的其他synchronized方法或代码块。可访问非synchronized修饰的方法
ReentrantLock:
- 使用起来相对复杂,需要手动获取和释放锁,容易忘记释放锁导致死锁。
- 由于使用了底层的 CAS 操作,性能相对于
Synchronized略低。
应用
Synchronized 更简单易用,适用于一般的同步场景, ReentrantLock 提供了更多的灵活性和功能选项,适用于更复杂的同步需求。选择哪种锁取决于具体的应用场景和需求。
CAS
原理
CAS(Compare and Swap)是乐观锁机制,是一种原子操作,用于实现并发控制。
在进行更新操作的时候,先读取目标数据和版本号或者mark值,然后在写入的时候,使用之前读取到的目标数据和版本号或者mark值,和heap中的目标数据和版本号或者是mark值,如果都一致的话,更新数据,不一致则自旋重试
优点
- 无锁:CAS是一种无锁的操作,不需要像传统的锁机制一样阻塞线程,避免了锁的开销和线程上下文切换的开销。
- 高效:在没有竞争的情况下,CAS可以快速完成对共享变量的操作,因为它不涉及真正的加锁和解锁过程。
缺点
ABA问题:在CAS操作中,目标数据可能在操作过程中发生多次修改,然后又回到了原来的值(例如A→B→A),这样CAS操作会认为目标数据没被改过。为解决ABA问题,需要用版本号来区分不同的修改。
应用
java中乐观锁,自旋锁,轻量级锁基于CAS机制不通过调用操作系统API,在用户态实现锁机制
本文介绍了乐观锁、悲观锁的概念和应用场景,包括数据库和Java中的实现。自旋锁作为一种优化方案,其优缺点也得到了阐述。此外,文章讨论了锁升级的过程,从偏向锁到重量级锁的转变,并对比了synchronized与ReentrantLock的异同。最后,提到了CAS(CompareandSwap)操作在无锁并发控制中的作用。

被折叠的 条评论
为什么被折叠?



