常见的锁策略
目录
1.乐观锁VS悲观锁
2.轻量级锁VS重量级锁
3.自旋锁VS挂起等待锁
4.互斥锁VS读写锁
5.公平锁VS非公平锁
6.可重入锁VS不可重入锁
7.以synchronized为例
1.乐观锁VS悲观锁
乐观锁:是一种基于冲突检测的策略,它假设多个线程同时访问共享资源时,冲突并不常见。乐观锁的特点如下:
- 无锁操作:在读取和修改数据时,线程不会显式地加锁,而是在更新时检查数据是否已被其他线程修改。
- 冲突检测:当一个线程准备更新数据时,它会检查数据是否自上次读取以来被其他线程修改过。如果数据没有被修改,线程会进行更新并结束乐观锁的过程。如果数据已经被修改,线程会采取一些措施,如重新尝试或使用其他同步机制。
- 使用场景:乐观锁适用于读操作远多于写操作,且冲突不频繁的场景。它可以减少锁的开销,提高系统的性能。
悲观锁:是一种基于冲突预防的策略,它假设多个线程同时访问共享资源时,冲突是常见的。悲观锁的特点如下:
- 显式加锁:在读取和修改数据之前,线程会显式地加锁,确保在同一时刻只有一个线程可以访问共享资源。
- 阻塞性:如果一个线程已经持有锁,其他线程必须等待直到锁被释放。
- 使用场景:悲观锁适用于写操作较多,或者冲突较频繁的场景。它可以防止数据不一致的问题,但可能会降低系统的并发性能。
乐观锁:预测锁竞争不是很激烈;
悲观锁:预测锁竞争会很激烈;
这里的乐观和悲观的唯一区分是主要看预测锁竞争激烈程度而言。
2.轻量级锁VS重量级锁
轻量级锁:如其名所示,设计上旨在减少加锁和解锁时的系统开销,以提供更高效的同步操作。这种类型的锁通常用在竞争不是很激烈的场景下。轻量级锁的特点如下:
- 低开销: 轻量级锁的设计使得在没有竞争或竞争较少的情况下,它的性能消耗远低于重量级锁。
- 适应性: 许多轻量级锁在开始时的确是乐观锁,它们假设冲突较少,只在必要时升级为更重量级的锁。
- 无阻塞: 在不涉及激烈竞争的情况下,轻量级锁能够在不阻塞线程的情况下提供同步保障。
- 适用场景: 轻量级锁适用于那些对性能要求较高且冲突不频繁的应用场景。
重量级锁:与轻量级锁相对的是重量级锁,它提供了严格的线程同步,但相应的系统开销也更大。重量级锁的特点如下:
- 高开销: 重量级锁在设计和实现上更为复杂,涉及操作系统层面的互操作,因此其加锁和解锁的开销相对较大。
- 严格的线程安全: 重量级锁保证了在高竞争环境下的线程安全,防止出现数据不一致的问题。
- 阻塞性: 在多线程竞争激烈的场合,重量级锁可能会导致线程阻塞,等待锁的释放。
- 适用场景: 重量级锁适用于写操作频繁或冲突激烈的场景,在这些场景下,它能够提供可靠的同步保障。
关联性
乐观锁多数情况下可以看作是一种轻量级锁,因为它通常避免了显式的锁操作,只是在必要时进行检查和可能的补偿措施。悲观锁则更倾向于使用重量级锁,尤其是在预计会发生激烈竞争的环境中,以确保数据的安全和一致性。
轻量级锁:轻量级锁加锁解锁开销比较小,效率更高;
重量级锁:重量级锁加锁解锁开销比较大,效率更低;
多数情况下,乐观锁,也是一个轻量级锁(不完全保证);
多数情况下,悲观锁,也是一个重量级锁(不完全保证)。
3.自旋锁VS挂起等待锁
自旋锁:是一种典型的轻量级锁,它的基本思想是:当一个线程尝试获取锁时,如果锁已经被其他线程持有,那么该线程会不断地循环检查锁是否可用。由于线程在等待锁的过程中不会进入阻塞状态,因此自旋锁适用于那些执行时间较短的场景;
挂起等待锁:是一种典型的重量级锁,它的工作原理是:当一个线程尝试获取锁时,如果锁已经被其他线程持有,那么该线程会被挂起并进入阻塞状态,直到锁被释放。挂起等待锁适用于那些执行时间较长或者需要等待外部资源的场景。
为什么挂起等待锁,会节省CPU资源?
挂起等待锁能够节省CPU资源的原因主要在于其设计机制允许线程在无法获取锁时进入休眠状态,从而让出CPU资源给其他线程或进程使用。
4.互斥锁VS读写锁
互斥锁:类似于synchronized关键字提供的锁,它提供了加锁和解锁两个基本操作。互斥锁的特点如下:
- 排他性:当一个线程对资源加锁后,其他线程必须等待该锁被释放才能访问该资源。这意味着互斥锁具有排他性,即同一时间只允许一个线程持有锁。
- 阻塞性:如果一个线程已经对某个资源加锁,其他尝试对该资源加锁的线程将会被阻塞,直到拥有锁的线程释放锁为止。
- 使用场景:互斥锁适用于多个线程之间存在相互排斥关系,需要确保在同一时刻只有一个线程能够访问特定资源的场景。
读写锁:是一种更为复杂的锁机制,它提供了读加锁、写加锁和解锁三种操作。读写锁的特点如下:
- 共享性:读锁可以被多个线程同时持有,允许多个线程并发读取资源,只要没有线程持有写锁。
- 排他性:写锁具有排他性,当一个线程持有写锁时,其他线程无法获取读锁或写锁,以确保数据的安全。
- 读写互斥:当有线程持有写锁时,其他线程无法获取读锁;同样,如果有线程持有读锁,其他线程也无法获取写锁。
- 使用场景:读写锁适用于读操作远多于写操作的场景,它允许多个线程并发读取资源,同时保证写操作的独占性。
互斥锁:类似于synchronized关键字提供的锁,它提供了加锁和解锁两个基本操作,如果一个线程加锁,另一个线程再尝试加锁,就会阻塞等待;
读写锁:提供了读加锁、写加锁和解锁三种操作。
5.公平锁VS非公平锁
公平锁:是一种基于先来先服务的调度策略,它确保等待时间最长的线程最先获得锁。公平锁的特点如下:
- 公平性:当多个线程同时请求同一把锁时,按照它们到达的顺序进行排队,最早到达的线程将首先获得锁。
- 阻塞性:如果一个线程已经持有锁,其他线程必须等待直到锁被释放。
- 使用场景:公平锁适用于需要保证所有线程都能公平地访问共享资源的场景,避免饥饿和长时间等待的情况发生。
非公平锁:是一种基于竞争的调度策略,它允许线程直接尝试获取锁,而不考虑等待队列中的线程顺序。非公平锁的特点如下:
- 非公平性:当多个线程同时请求同一把锁时,哪个线程能够获取到锁取决于操作系统的调度策略,而不是按照到达顺序。
- 阻塞性:如果一个线程已经持有锁,其他线程必须等待直到锁被释放。
- 使用场景:非公平锁适用于那些不需要严格公平性的场景,例如高并发环境下的性能优化。
公平锁:先来后到,在等待队列中,最早来的队列先获得线程;
非公平锁:雨露均沾,共同竞争线程。
6.可重入锁VS不可重入锁
可重入锁:允许一个线程在已经持有某把锁的情况下,再次获取该锁而不会导致死锁或阻塞。这种锁的设计是为了支持递归操作,即一个方法在其执行过程中再次调用自身。可重入锁的特点如下:
- 递归友好: 可重入锁可以在同一个线程中被多次请求而不会阻塞,这使得递归操作成为可能。
- 安全性: 可重入锁通常实现为悲观锁,以确保在竞争激烈的环境下的线程安全。
- 适用场景: 可重入锁适用于需要递归操作的场景,以及那些需要确保线程安全性的高竞争环境。
不可重入锁:不允许一个线程在已经持有某把锁的情况下再次获取该锁,如果尝试这样做,线程会阻塞或导致死锁。不可重入锁的特点如下:
- 非递归性: 不可重入锁不支持递归操作,因为同一个线程不能多次获取同一把锁。
- 潜在死锁风险: 如果一个线程试图重新获取它已经持有的锁,它将会阻塞或进入死锁状态。
- 适用场景: 不可重入锁适用于简单的同步控制,其中递归调用不是问题,且可以通过其他方式避免死锁。
可重入锁:一个线程针对一把锁连续加锁两次,出现死锁;
不可重入锁:一个线程针对一把锁连续加锁多次,不出现死锁。
7.以synchronized为例
1.synchronized既是一个悲观锁,也是一个乐观锁,默认为乐观锁,但是当发现当前锁竞争比较激烈,就会变成悲观锁;
2.synchronized既是一个轻量级锁,也是一个重量级锁,默认为轻量级锁(基于自旋锁的方式实现),但是当发现当前锁竞争比较激烈,就会变成重量级锁(基于挂起等待锁的方式实现);
3.synchronized不是读写锁;
4.synchronized是非公平锁;
5.synchronized是可重入锁。