可重入锁 不可重入锁
在Java 5.0中,增加了一个新功能以增强内部锁定功能,称为可重入锁定。 在此之前,“同步”和“易失性”是实现并发的手段。
public synchronized void doAtomicTransfer(){//enter synchronized block , acquire lock over this object.operation1()operation2();
} // exiting synchronized block, release lock over this object.
同步使用固有锁或监视器。 Java中的每个对象都有一个与之关联的固有锁。 每当线程尝试访问同步的块或方法时,它都会获取该对象的固有锁定或监视器。 在使用静态方法的情况下,线程获取对类对象的锁定。
就代码编写而言,内在锁定机制是一种干净的方法,对于大多数用例而言,它都是不错的选择。 那么,为什么我们需要显式锁的其他功能? 让我们讨论。
内部锁定机制可能具有一些功能限制,例如:
- 无法中断等待获取锁的线程。 (间断锁定)
- 如果不想永远等待,就不可能尝试获取锁。 (尝试锁定)
- 无法实现非块结构的锁定规则,因为必须在获取它们的同一块中释放固有锁。
除此之外,ReentrantLock还支持锁轮询和支持超时的可中断锁等待。 ReentrantLock还支持可配置的公平性策略,从而允许更灵活的线程调度。
让我们看一下ReentrantLock类(实现Lock)实现的几种方法:
void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
.....
让我们尝试了解这些用法,看看我们可以得到什么好处。
- 轮询和定时锁获取:
让我们看一下示例代码:
public void transferMoneyWithSync(Account fromAccount, Account toAccount,float amount) throws InsufficientAmountException {synchronized (fromAccount) {// acquired lock on fromAccount Objectsynchronized (toAccount) {// acquired lock on toAccount Objectif (amount > fromAccount.getCurrentAmount()) {throw new InsufficientAmountException("Insufficient Balance");} else {fromAccount.debit(amount);toAccount.credit(amount);}}}}
在上面的transferMoney()方法中,当2个线程A和B几乎同时尝试转移资金时,可能会出现死锁。
A: transferMoney(acc1, acc2, 20); B: transferMoney(acc2, acc1 ,25);
线程A可能已获得对acc1对象的锁定,并正在等待获取对acc2对象的锁定,同时线程B已获得了对acc2对象的锁定,并且正在等待对acc1的锁定。 这将导致死锁,并且必须重新启动系统!
但是,有一种避免这种情况的方法,也就是所谓的“锁定排序”,我个人认为这有点复杂。
ReentrantLock使用tryLock()方法实现了一种更干净的方法。 这种方法称为“定时和轮询锁定获取”。 如果您无法获取所有必需的锁,释放已获取的锁并重试,它可以让您重新获得控制权。
因此,使用tryLock我们将尝试获取两个锁,如果无法同时获取这两个锁,则如果已经获取了其中之一,则释放它,然后重试。
public boolean transferMoneyWithTryLock(Account fromAccount,Account toAccount, float amount) throws InsufficientAmountException, InterruptedException {// we are defining a stopTimelong stopTime = System.nanoTime() + 5000;while (true) {if (fromAccount.lock.tryLock()) {try {if (toAccount.lock.tryLock()) {try {if (amount > fromAccount.getCurrentAmount()) {throw new InsufficientAmountException("Insufficient Balance");} else {fromAccount.debit(amount);toAccount.credit(amount);}} finally {toAccount.lock.unlock();}}} finally {fromAccount.lock.unlock();}}if(System.nanoTime() < stopTime)return false;Thread.sleep(100);}//while}
在这里,我们实现了定时锁,因此,如果在指定时间内无法获取锁,则transferMoney方法将返回失败通知并正常退出。
我们还可以使用此概念来维护时间预算活动。
- 可中断锁获取:
可中断的锁获取允许在可取消的活动中使用锁。
lockInterruptible方法使我们能够尝试获取锁,但可用于中断。 所以基本上这意味着 它允许线程立即响应从另一个线程发送给它的中断信号。
当我们要向所有等待的锁发送KILL信号时,这将很有帮助。让我们看一个例子,假设我们有一条共享的行来发送消息,我们希望以这样的方式设计它:如果另一个线程来了并且中断了当前线程,则应该释放锁并执行退出或关闭操作以取消当前任务。
public boolean sendOnSharedLine(String message) throws InterruptedException{lock.lockInterruptibly();try{return cancellableSendOnSharedLine(message);} finally {lock.unlock();}}private boolean cancellableSendOnSharedLine(String message){ .......
定时tryLock也可响应中断。
- 非块结构锁定:
在固有锁中,获取释放对是块结构的,即,无论控制如何退出该块,始终在获取该锁的同一基本块中释放该锁。
外在锁提供了进行更明确控制的功能。
使用外部锁可以更轻松地实现某些概念,例如“锁紧皮带”。 在哈希混和集合和链接列表中可以看到一些用例。 - 公平:
ReentrantLock构造函数提供两个公平选项的选择:创建非公平锁或公平锁。 公平锁的线程只能在它们所要求的顺序获取锁,而一个不公平的锁允许锁获取它反过来的,这就是所谓的驳运 (打破了队列和获取锁,当它变得可用)。
由于挂起和恢复线程的开销,公平锁定会带来巨大的性能成本。 在某些情况下,恢复挂起的线程与实际运行之间会有明显的延迟。 让我们看一下情况:
A -> holds lock B -> has requested and is in suspended state waiting for A to release lock C -> requests the lock at the same time when A releases the lock, C has not yet gone to suspended state.
由于C尚未处于挂起状态,因此它有可能获得A释放的锁,使用它,然后在B甚至还没有唤醒之前释放它。 因此,在这种情况下,不公平锁定具有明显的性能优势。
内部锁和外部锁在内部具有相同的锁定机制,因此性能的提高纯粹是主观的。 这取决于我们上面讨论的用例。 外在锁提供了更明确的控制机制,可以更好地处理死锁,饥饿等。我们将在以后的博客中看到更多示例。
翻译自: https://www.javacodegeeks.com/2013/11/what-are-reentrant-locks.html
可重入锁 不可重入锁