![可重入锁](https://i-blog.csdnimg.cn/blog_migrate/93cf7754c16c57e63e6b0fd5254afd48.png)
可重入锁
在Java 5.0中,增加了一个新功能以增强内部锁定功能,称为Reentrant Lock。 在此之前,“同步”和“易失”是实现并发的手段。
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 Object synchronized (toAccount) { // acquired lock on toAccount Object if (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 stopTime long 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
可重入锁