比较Java Semaphore和ReentrantLock

752 阅读5分钟

通过实际的例子来了解什么是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的计数值可以是01。这意味着二进制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.SemaphoreReentrantLock的区别

现在让我们看一下这两个类的主要区别。

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 修改

任何线程 都可以使用Semaphoreacquire()release() 方法来修改它的可用许可。

只有通过lock()方法拥有资源的当前所有者线程可以修改ReentrantLock,而其他线程不允许这样做。

5.什么时候使用?

Semaphores可以用于非所有权-释放语义,即不止一个Thread 可以进入一个关键部分,并且不需要锁定机制来锁定一个共享资源。根据设计,Semaphore 对哪个线程调用acquisition()release()方法是盲目的,它所关心的是许可成为可用的。

如果我们需要可重入互斥或一个简单的互斥 ,那么 ReentrantLock是最好的选择。可重入锁 提供了对锁机制更好的控制,并且允许每次只有一个线程访问关键部分,从而提供了同步性,并消除了在多线程应用程序中工作时的数据不一致问题。

6.总结

我们已经通过实例了解了SemaphoresReentrantLock。我们还看到了两者之间的一些主要区别,以及如何根据我们的需要和要求在它们之间进行选择。