java中的锁主要分为悲观锁与乐观锁、公平锁与非公平锁、可重入锁与不可重入锁、独占锁与共享锁等类型。1.悲观锁如synchronized和reentrantlock适用于写多场景,每次操作都加锁保证数据一致性;2.乐观锁通过版本号或cas实现,适用于读多写少的场景,提高吞吐量;3.公平锁按申请顺序获取锁避免饥饿现象,但性能较低,而非公平锁效率高但可能导致线程饥饿;4.可重入锁允许同一线程多次获取同一把锁,避免死锁,如synchronized和reentrantlock;5.独占锁一次只能被一个线程持有,而共享锁允许多个线程同时访问,如readwritelock的读锁;6.自旋锁通过循环等待锁释放,适用于锁持有时间短的场景;7.jvm还对锁进行了优化,引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁,根据竞争激烈程度进行状态转换,以提升性能。
Java中的锁种类繁多,理解它们能帮助我们写出更高效、更可靠的并发程序。简单来说,可以从不同的维度进行分类,比如悲观锁/乐观锁、公平锁/非公平锁、可重入锁/不可重入锁、独占锁/共享锁等等。掌握这些锁的特性,才能在实际场景中选择最合适的锁,避免死锁和性能瓶颈。
悲观锁与乐观锁的本质区别和应用场景
悲观锁,顾名思义,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。悲观锁适用于写操作非常多的场景,先加锁可以保证写操作时数据正确。
立即学习“Java免费学习笔记(深入)”;
乐观锁则相对乐观,它假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式通常是使用版本号机制或者CAS算法。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式:CAS实现的。乐观锁适用于读多写的场景,这样可以提高程序的吞吐量。
公平锁与非公平锁的区别与选择
公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队,先到先得。而非公平锁则允许线程“插队”,即后来的线程可能比先来的线程更快获取到锁。
Java中的ReentrantLock可以通过构造函数指定是否为公平锁。默认情况下,ReentrantLock是非公平锁。synchronized也是一种非公平锁。
公平锁的优点是避免了“饥饿”现象,每个线程都有机会获得锁。缺点是效率相对较低,因为需要维护一个等待队列,并且线程切换的开销也比较大。非公平锁的优点是效率高,吞吐量大,但缺点是可能导致某些线程长时间无法获取到锁,产生“饥饿”现象。
实际应用中,如果对公平性要求不高,优先选择非公平锁,以提高性能。如果需要保证每个线程都有机会执行,避免“饥饿”,则选择公平锁。
可重入锁与不可重入锁:理解锁的嵌套使用
可重入锁是指,当一个线程已经获取到锁之后,允许它再次获取该锁,而不需要释放之前的锁。这意味着同一个线程可以多次进入被该锁保护的代码块。
Java中的synchronized和ReentrantLock都是可重入锁。可重入锁避免了死锁的发生,例如:
public class ReentrantLockExample { ReentrantLock lock = new ReentrantLock(); public void outer() { lock.lock(); try { System.out.println("Outer method acquired lock"); inner(); } finally { lock.unlock(); System.out.println("Outer method released lock"); } } public void inner() { lock.lock(); try { System.out.println("Inner method acquired lock"); } finally { lock.unlock(); System.out.println("Inner method released lock"); } } public static void main(String[] args) { ReentrantLockExample example = new ReentrantLockExample(); example.outer(); } }
如果ReentrantLock不是可重入锁,那么inner方法在尝试获取锁时,会因为锁已经被outer方法持有而阻塞,导致死锁。
独占锁与共享锁:控制资源访问权限
独占锁(也称为互斥锁)是指一次只能被一个线程持有的锁。当一个线程获取了独占锁之后,其他线程必须等待该线程释放锁才能获取。Java中的synchronized和ReentrantLock都是独占锁。
共享锁是指可以被多个线程同时持有的锁。多个线程可以并发地读取被共享锁保护的资源,但如果需要修改资源,则必须获取独占锁。Java中的ReadWriteLock接口提供了读写锁的实现,读锁是共享锁,写锁是独占锁。
读写锁适用于读多写少的场景,可以提高程序的并发性能。多个线程可以同时读取数据,只有在写数据时才需要进行互斥。
深入理解自旋锁:优化短时间锁竞争场景
自旋锁是一种特殊的锁,它不会让线程进入阻塞状态,而是让线程循环等待锁的释放。当锁被其他线程持有时,尝试获取锁的线程会不断地循环检查锁是否可用,直到获取到锁为止。
自旋锁适用于锁的持有时间非常短的场景。如果锁的持有时间较长,自旋锁会导致线程一直占用CPU资源,造成性能浪费。
Java中并没有直接提供自旋锁的实现,但可以使用CAS算法来实现简单的自旋锁。例如:
public class SpinLock { private AtomicReference<Thread> owner = new AtomicReference<>(); public void lock() { Thread current = Thread.currentThread(); while (!owner.compareAndSet(null, current)) { // 自旋等待 } } public void unlock() { Thread current = Thread.currentThread(); owner.compareAndSet(current, null); } }
CAS算法的性能取决于CPU的架构和锁竞争的激烈程度。在高并发场景下,CAS算法可能会出现大量的冲突,导致性能下降。
锁升级:从偏向锁到重量级锁
为了提高锁的性能,JVM对锁进行了优化,引入了锁升级的概念。锁升级是指锁的状态会根据锁竞争的激烈程度进行升级,从低级别的锁到高级别的锁。
锁的级别从低到高依次为:偏向锁、轻量级锁、重量级锁。
理解锁升级的过程,可以帮助我们更好地理解JVM的锁优化机制,并根据实际场景选择合适的锁。
以上就是Java中锁的分类有哪些 详解Java中的各种锁机制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号