通过实际的例子来了解什么是semaphore和Reentrant lock。我们还将探讨两者之间的一些主要区别,以及在多线程应用程序中使用这些东西的用例。
1.什么是 "寄托"?
Semaphore是一个线程 同步结构,它作为一个具有计数器功能的锁。 Semaphore类存在于java.util.concurrent 包中,实现了Serializable接口,并从那时起就一直存在。 Java 1.5版本.
从概念上讲,Semaphore维护一组许可,由一个可以递增或递减的计数器值表示。
TheSemaphore.acquire(),从中抽取一个许可,并将计数器的值递减1。- Semaphone.
release()方法将许可返回给Semaphore ,并将计数器的值增加1。
如果许可证用完了,或者计数器的值达到了0,就意味着所有的共享资源都被其他线程使用了,那么这个 *acquire()*方法会阻止当前线程,直到Semaphore获得许可。
在Java中存在不同类型的semaphores,例如
- 二进制暂存器
- 计数占位器
- 定时占位器
- 有边界的半步态(Bounded Semaphore)
在这些方法中。 二进制触发器 是日常编码中使用最多的。二进制semaphore的计数值可以是0或1。这意味着二进制Semaphore保护对单一共享资源的访问,并提供相互排斥,即每次只允许一个线程访问一个关键资源。
现在让我们看一个例子,看看如何创建一个二进制的Semaphore以及如何使用它的方法。
Semaphore binarySemaphore = new Semaphore(1);
// Acquiring semaphore
binarySemaphore.acquire();
// Printing Available Permits
System.out.println(binarySemaphore.availablePermits()) // 0
// Releasing semaphore
binarySemaphore.release();
// Printing Available Permits
System.out.println(binarySemaphore.availablePermits()) // 1
让我们通过伪代码来了解如何使用一个Semaphore。
class X {
private final Semaphore semaphore = new Semaphore(1);
// ...
public void m() {
try {
semaphore.acquire();
// ... method body
} finally {
semaphore.release();
}
}
}
2.什么是可重入锁(ReentrantLock)?
ReentrantLock 实现了 锁定接口,其工作原理几乎类似于 同步的关键字,但具有扩展功能。
可重入的 意思是,一个线程可以多次获得相同的锁,而没有任何问题。
- 在内部,每当一个Thread调用
lock()方法时,Reentrant Lock 就会增加Thread 的计数器。 - 每当Thread调用
unlock()方法时,它就会减去相同的计数器值。
只有当计数器达到0时,锁才会被线程释放,也就是说,当一个线程重新进入该锁时,它必须请求解锁相同的次数来释放该锁。
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock(); // counter=1
reentrantLock.lock(); //counter=2
// Check if the object is locked
System.out.println(reentrantLock.isLocked()); // true
// Check if the lock acquired by current Thread
System.out.println(reentrantLock.isHeldByCurrentThread()); // true
// Release the acquired lock using unlock()
reentrantLock.unlock(); //counter=1
System.out.println(reentrantLock.isLocked()); // true
System.out.println(reentrantLock.getHoldCount()); // 1
// Release the acquired lock again
reentrantLock.unlock(); //counter=0
System.out.println(reentrantLock.isLocked()); // false
System.out.println(reentrantLock.getHoldCount()); // 0
让我们看看如何使用它的伪代码。
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
}
}
3.Semaphore和ReentrantLock的区别
现在让我们看一下这两个类的主要区别。
3.1.可重入的性质
Semaphores在本质上是非可重入的 ,这意味着我们不能在同一个线程中第二次获得Semaphore。试图这样做会导致死锁(一个线程与自己死锁)。
另一方面, 可重入锁在本质上是可重入的,允许一个线程使用lock() 方法多次锁定一个特定的资源。
3.2 同步机制
半吊子 很适合信号传递(信号机制),线程使用acquire()&release() 方法来标记访问关键资源的开始和结束。
Reentrant Lock 使用锁定机制, 使用lock() 方法 锁定一个特定的资源 ,在对该资源进行特定操作后,使用unlock() 方法释放该锁。
3.3死锁 恢复
半锁提供了一个强大的死锁恢复机制,因为它使用了一个非所有权的释放机制,因此任何线程 都可以释放一个许可,以恢复一个卡住或等待的线程 的死锁 情况。
在 Reentrant Lock的情况下, 死锁恢复是有点困难的,因为它使用线程 对资源的所有权,通过物理锁定它,只有所有者线程 可以解锁该资源。如果所有者Thread进入无限等待或睡眠状态,就不可能释放该特定资源的锁,从而导致死锁 情况。
3.4 抛出IllegalMonitorStateException
在Semaphores中,没有线程 拥有获取或释放许可的所有权,所以任何线程都可以调用release() 方法来释放任何其他线程 的许可,没有线程 会引发 IllegalMonitorStateException。
在可重入 锁中,一个Thread 通过调用lock() 方法成为一个关键共享资源的所有者,如果其他Thread在没有拥有锁的情况下调用unlock() 方法,那么 它将会产生 IllegalMonitorStateException。
3.5 修改
任何线程 都可以使用Semaphore的acquire() 和release() 方法来修改它的可用许可。
只有通过lock()方法拥有资源的当前所有者线程可以修改ReentrantLock,而其他线程不允许这样做。
5.什么时候使用?
Semaphores可以用于非所有权-释放语义,即不止一个Thread 可以进入一个关键部分,并且不需要锁定机制来锁定一个共享资源。根据设计,Semaphore 对哪个线程调用acquisition()和release()方法是盲目的,它所关心的是许可成为可用的。
如果我们需要可重入互斥或一个简单的互斥 ,那么 ReentrantLock是最好的选择。可重入锁 提供了对锁机制更好的控制,并且允许每次只有一个线程访问关键部分,从而提供了同步性,并消除了在多线程应用程序中工作时的数据不一致问题。
6.总结
我们已经通过实例了解了Semaphores和ReentrantLock。我们还看到了两者之间的一些主要区别,以及如何根据我们的需要和要求在它们之间进行选择。