Java并发 - ReentrantLock锁
带着问题阅读 1.什么是可重入锁?可重入锁解决什么问题? 2.ReentrantLock的核心是AQS,它是怎么实现的? 3.ReentrantLock中的公平锁和非公平锁?
1. ReentrantLock源码解析 graph BT;
B(Sync) --> A(AbstractQueuedSynchronizer)
subgraph extends
C(NonfairSync) -->|extends| B
D(FairSync) --> |extends|B
end
ReentrantLock是Java中实现可重入锁的一个重要类,下面是对其源码的简要解析。请注意,这里只是对关键部分进行了概要说明,具体实现细节可能会有更多复杂性。
公平锁和非公平锁
ReentrantLock可以是公平锁或非公平锁。在公平锁模式下,线程按照申请锁的顺序来获取锁,不会产生饥饿现象。而非公平锁则允许线程在任意时刻争夺锁,可能导致某些线程长时间无法获取锁,存在饥饿现象。
1.1 Sync 这个组件是基于AQS(AbstractQueuedSynchronizer)的抽象类,用于实现ReentrantLock的同步机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L ; abstract void lock () ; final boolean nonfairTryAcquire (int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0 ) { if (compareAndSetState(0 , acquires)) { setExclusiveOwnerThread(current); return true ; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; } protected final boolean tryRelease (int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException (); boolean free = false ; if (c == 0 ) { free = true ; setExclusiveOwnerThread(null ); } setState(c); return free; } protected final boolean isHeldExclusively () { return getExclusiveOwnerThread() == Thread.currentThread(); } final ConditionObject newCondition () { return new ConditionObject (); } final Thread getOwner () { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount () { return isHeldExclusively() ? getState() : 0 ; } final boolean isLocked () { return getState() != 0 ; } private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0 ); } }
方法
作用
lock
lock方法是ReentrantLock类中用于获取锁的抽象方法。具体的实现在ReentrantLock的非公平锁和公平锁中有所不同
nonfairTryAcquire
nonfairTryAcquire方法是非公平锁的尝试获取锁的实现。它首先检查当前同步状态是否为0,如果是,则通过CAS操作将其设置为acquires,表示获取锁成功。
tryRelease
tryRelease方法是释放锁的实现。它首先检查当前线程是否为锁的持有者,如果不是则抛出IllegalMonitorStateException异常。
isHeldExclusively
用于检查当前线程是否是锁的持有者。这里直接比较当前线程和独占锁的持有者线程,如果相等,则当前线程是持有者。
newCondition
用于创建一个与ReentrantLock关联的Condition对象,用于支持更灵活的线程等待和唤醒操作
getOwner
返回当前持有锁的线程,如果锁未被持有则返回null
getHoldCount
返回当前线程持有锁的次数,如果不是持有者则返回0
isLocked
用于检查锁是否被持有,即同步状态是否不为0
readObject
用于反序列化,将实例的状态重置为未锁定状态
Sync是基于AbstractQueuedSynchronizer的抽象类,AQS是ReentrantLock的核心组件,它提供了一种便于实现各种同步器的框架。它基于FIFO队列实现了一套多线程同步的抽象框架,通过状态的获取和释放来实现线程的同步。
实现原理: AQS使用一个int型的状态来表示资源的占用情况,通过CAS操作来保证状态的原子性。当线程尝试获取锁时,如果锁已被占用,线程会被加入到等待队列中。释放锁时,线程将释放锁的状态,并唤醒等待队列中的线程。
1.2 NonfairSync NonfairSync是ReentrantLock中非公平锁的实现,它继承了Sync类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L ; final void lock () { if (compareAndSetState(0 , 1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1 ); } protected final boolean tryAcquire (int acquires) { return nonfairTryAcquire(acquires); } }
1.3 FairSync FairSync是ReentrantLock中公平锁的实现,它继承了Sync类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L ; final void lock () { acquire(1 ); } protected final boolean tryAcquire (int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0 ) { if (!hasQueuedPredecessors() && compareAndSetState(0 , acquires)) { setExclusiveOwnerThread(current); return true ; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; } }
可重入锁是一种支持线程重复获取同一把锁的锁机制。当一个线程已经获得某个锁时,它可以再次获取该锁,而不会被阻塞。这种锁机制允许同一线程在持有锁的情况下多次进入同步块或方法,而不会被自己持有的锁所阻塞。
可重入锁解决了两个主要问题:
避免死锁: 在多线程环境中,如果一个线程已经获得了锁A,而在持有锁A的情况下又试图获取锁B,而另一个线程已经获得了锁B,又试图获取锁A,就会发生死锁。可重入锁允许同一线程在持有锁的情况下继续获取其他锁,避免了死锁的发生。
支持递归调用: 在某些情况下,同一线程需要多次进入同步块或方法,而不希望被自己持有的锁所阻塞。可重入锁允许同一线程多次获取同一把锁,使得递归调用的情况更容易管理。
2. ReentrantLock的使用示例 1.1 公平锁示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class FairLockExample { private static final Lock fairLock = new ReentrantLock (true ); public static void main (String[] args) { Runnable fairTask = () -> { try { fairLock.lock(); System.out.println(Thread.currentThread().getName() + " acquired the lock" ); Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + " releasing the lock" ); fairLock.unlock(); } }; for (int i = 1 ; i <= 5 ; i++) { new Thread (fairTask, "Thread-" + i).start(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 Connected to the target VM, address: '127.0.0.1:62892', transport: 'socket' Thread-1 acquired the lock Thread-1 releasing the lock Thread-2 acquired the lock Thread-2 releasing the lock Thread-3 acquired the lock Thread-3 releasing the lock Thread-4 acquired the lock Thread-4 releasing the lock Thread-5 acquired the lock Thread-5 releasing the lock Disconnected from the target VM, address: '127.0.0.1:62892', transport: 'socket'
1.2 非公平锁示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class NonfairLockExample { private static final Lock nonfairLock = new ReentrantLock (); public static void main (String[] args) { Runnable nonfairTask = () -> { try { nonfairLock.lock(); System.out.println(Thread.currentThread().getName() + " acquired the lock" ); Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + " releasing the lock" ); nonfairLock.unlock(); } }; for (int i = 1 ; i <= 5 ; i++) { new Thread (nonfairTask, "Thread-" + i).start(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 Connected to the target VM, address: '127.0.0.1:50980', transport: 'socket' Thread-1 acquired the lock Thread-1 releasing the lock Thread-3 acquired the lock Thread-3 releasing the lock Thread-2 acquired the lock Thread-2 releasing the lock Thread-4 acquired the lock Thread-4 releasing the lock Thread-5 acquired the lock Thread-5 releasing the lock Disconnected from the target VM, address: '127.0.0.1:50980', transport: 'socket'
3. ReentrantLock的应用场景
替代synchronized关键字: ReentrantLock 提供了比 synchronized 更灵活的锁控制,允许更复杂的线程同步结构。在需要更多控制权和灵活性的场景下,可以使用 ReentrantLock 替代 synchronized。
可中断的锁请求: ReentrantLock 提供了可中断的锁请求功能,即线程可以响应中断信号而不是无限等待锁。这对于避免死锁等问题很有帮助。
超时的锁请求: ReentrantLock 允许在尝试获取锁时设置超时时间,避免线程无限期地等待锁。
公平性控制: ReentrantLock 提供了可选的公平锁和非公平锁。在公平锁模式下,等待时间较长的线程更有可能获取锁,有助于避免饥饿情况。
Condition条件变量的使用: ReentrantLock 支持通过 newCondition 方法创建与锁关联的 Condition 对象,从而实现更灵活的线程等待和通知机制。
可重入性控制: ReentrantLock 支持线程的可重入性,同一线程可以多次获取同一把锁,而不会造成死锁。
精确的锁释放控制: ReentrantLock 提供了对锁释放的精确控制,可以在特定条件下选择是否释放锁,这对于实现一些特殊的同步逻辑很有帮助。
性能优化: 在某些情况下,使用 ReentrantLock 可以提供更好的性能,特别是在高并发环境下。
总的来说,ReentrantLock 适用于需要更灵活、更可控的线程同步机制的场景。在这些场景中,它提供了比传统的synchronized关键字更多的功能和选项。