Java中是否有互斥锁(Mutex)?

132

Java中是否有Mutex对象或创建它的方法?我之所以问是因为初始化为1个许可的Semaphore对象对我没有帮助。考虑以下情况:

try {
   semaphore.acquire();
   //do stuff
   semaphore.release();
} catch (Exception e) {
   semaphore.release();
}

如果在第一个获取(acquire)操作发生异常,则catch块中的释放(release)操作将增加许可数,从而使信号量不再是二进制信号量。

正确的方式会是什么呢?

try {
   semaphore.acquire();
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}

以上代码是否可以确保信号量是二进制的?


查看 java.util.concurrent.locks.AbstractQueuedSynchronizer 的 javadoc。它有一个编写 Mutex 类的示例。-dbednar - joe
你是通过实证研究发现这种行为的吗?如果在一个持有另一个许可的1个许可信号量上执行release(),实现是否会添加额外的许可? - Whimusical
8个回答

147

在Java中,任何对象都可以使用synchronized块作为锁。当发生异常时,它还会自动释放锁。

Object someObject = ...;

synchronized (someObject) {
  ...
}

您可以在这里了解更多信息:内置锁和同步


非常有帮助,但我想使用信号量。 - Noam Nevo
12
只需将使用信号量和使用synchronized的代码进行比较,您就会发现哪种更易读且更不容易出错。 - Vlad
25
如果你打算在不同的方法中释放锁(例如transaction.begin(); transaction.commit()),那么就不能使用synchronized关键字。 - Hosam Aly
它不是面向对象的,而更多地是低级同步。 - anshulkatta
1
当您查看此答案的代码时,还要注意someObject.wait(timeout)someObject.notify() - Daniel F

127

请参考此页面:http://www.oracle.com/technetwork/articles/javase/index-140767.html

它有一个略微不同的模式,这应该是您正在寻找的内容:

try {
  mutex.acquire();
  try {
    // do something
  } finally {
    mutex.release();
  }
} catch(InterruptedException ie) {
  // ...
}
在这种使用情况下,您只会在成功调用acquire()后调用release()

1
你如何定义互斥变量? - nil96

40

没有人明确提到过这一点,但是这种模式通常不适合使用信号量。原因是任何线程都可以释放信号量,但通常你只希望所有者线程(最初锁定的线程)能够解锁。对于这种情况,在Java中,我们通常使用可重入锁,可以像这样创建:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

private final Lock lock = new ReentrantLock(true);

通常的使用设计模式是:

  lock.lock();
  try {
      // do something
  } catch (Exception e) {
      // handle the exception
  } finally {
      lock.unlock();
  }
这里 是 Java 源代码中展示该模式的一个示例。
可重入锁具有支持公平性的额外好处。
只在需要非所有权释放语义时使用信号量。

9
这实际上应该是这个问题的(唯一)正确答案。清晰解释信号量和互斥锁之间的区别。使用count=1的信号量并不是互斥锁。 - Kaihua
4
很高兴有人指出这一点。对于独占访问某一资源,互斥锁是最佳选择。二进制信号量不是互斥锁,信号量应更多地用作信号传递机制。 - Shivam Tripathi
1
Rouble: 那么lock,例如ReentrantLock是一个mutex吗?我不确定为什么mutexbinary semaphore被视为相同的实体。Semaphore可以被任何线程释放,因此可能无法保护critical section。你有什么想法吗? - CuriousMind
1
@Kaihua:我非常认同你的想法。这个答案提供了关键的区别。 - CuriousMind

31
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


private final Lock _mutex = new ReentrantLock(true);

_mutex.lock();

// your protected code here

_mutex.unlock();

5
这种方法相比已有的解决方案有何优越之处?它是如何解决原问题提出者所遇到的问题的? - Martin
@Martin:“锁实现提供比使用同步方法和语句更广泛的锁定操作。”,来源:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html ……虽然你说得有道理。Argv的答案没有说明或解释这些操作。 - FrustratedWithFormsDesigner
4
这是一个递归互斥锁,允许同一线程进行多次重新加锁,这可能会有问题。一个“真正的”基本互斥锁(非递归的,C++风格)只允许同时加一次锁。如果你将代码行改为 private final ReentrantLock _mutex = ...,你可以使用getHoldCount()方法返回线程重新加锁的次数。(你也可以使用“Condition”来防止这种情况。请参见API。) - EntangledLoops
所以mutex只是一个锁。 - LookIntoEast

6

我认为你应该尝试使用以下方法:

在信号量初始化时:

Semaphore semaphore = new Semaphore(1, true);

在你的Runnable实现

try 
{
   semaphore.acquire(1);
   // do stuff

} 
catch (Exception e) 
{
// Logging
}
finally
{
   semaphore.release(1);
}

这就是我的做法,但我不太确定这是否正确。 - noBillSide
1
根据https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html#release(),“释放许可的线程不一定要通过调用acquire()来获取该许可。正确使用信号量是由应用程序中的编程约定确定的。”如果acquire()抛出异常,则finally中的release()将错误地释放一个许可。此线程中的其他示例显示了正确的流程。 - Brent K.

4

原帖中的错误在于将acquire()调用放置在了try循环内部。以下是使用“二进制”信号量(互斥锁)的正确方法:

semaphore.acquire();
try {
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}

1

每个对象的锁与Mutex / Semaphore设计略有不同。例如,没有正确实现遍历链接节点以释放先前节点的锁并捕获下一个节点的方法。但是,使用互斥锁很容易实现:

Node p = getHead();
if (p == null || x == null) return false;
p.lock.acquire();  // Prime loop by acquiring first lock.
// If above acquire fails due to interrupt, the method will
//   throw InterruptedException now, so there is no need for
//   further cleanup.
for (;;) {
Node nextp = null;
boolean found;
try { 
 found = x.equals(p.item); 
 if (!found) { 
   nextp = p.next; 
   if (nextp != null) { 
     try {      // Acquire next lock 
                //   while still holding current 
       nextp.lock.acquire(); 
     } 
     catch (InterruptedException ie) { 
      throw ie;    // Note that finally clause will 
                   //   execute before the throw 
     } 
   } 
 } 
}finally {     // release old lock regardless of outcome 
   p.lock.release();
} 

目前,在java.util.concurrent中没有这样的类,但是你可以在这里找到Mutext实现 Mutex.java。至于标准库,Semaphore提供了所有这些功能以及更多。


0

为了确保 Semaphore 是二进制的,您只需要在创建信号量时将许可证数量传递为 1。Javadocs 中有更详细的解释。


不要抱歉。它可以用作二进制信号量,但它并不是二进制信号量。如果你对其调用多次release()方法,将允许多个获取线程被释放。 - Jack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接