Java中的互斥锁和信号量是什么?它们的主要区别是什么?

118

互斥锁和信号量在Java中是什么?它们之间的主要区别是什么?


在编程中,什么情况下应该使用互斥锁(mutex),什么情况下应该使用信号量(semaphore)?在stackoverflow.com的类似问题中有相关查询。 - Karthik Balaguru
10个回答

150

遗憾的是,大家都错过了信号量和互斥锁之间最重要的区别,即“所有权”的概念。

信号量没有所有权的概念,这意味着任何线程都可以释放信号量(这本身可能会导致许多问题,但有助于“死亡检测”),而互斥锁具有所有权的概念(即你只能释放已获得的互斥锁)。

所以对于安全编程并发系统来说,所有权非常重要。我总是建议使用互斥锁而不是信号量(但这会带来性能影响)。

互斥锁也可以支持优先级继承(这可以帮助解决优先级反转问题)和递归(消除一种死锁类型)。

还应指出,有“二进制”信号量和“计数/通用”信号量之分。Java的信号量是计数信号量,因此允许将其初始化为一个大于1的值(而互斥锁只能拥有概念上的计数1)。其他帖子中已经指出了这种方法的有用性。

因此,总结一下:除非你需要管理多个资源,否则我始终建议使用互斥锁而不是信号量。


1
Feabhas的回答非常重要——互斥锁检查试图释放互斥锁的线程是否实际拥有它。我曾经在面试中遇到过这个问题,所以值得记住。 - andrew pate

119

信号量可以进行计数,而互斥锁只能计数到1。

假设您有一个线程正在运行来接受客户端连接。这个线程最多可以同时处理10个客户端。然后每个新客户端都会设置信号量,直到达到10. 当信号量有10个标志时,您的线程将不再接受新连接。

互斥锁通常用于保护资源。如果您的10个客户端可以访问系统的多个部分,则可以使用互斥锁来保护系统的部分,以便连接到该子系统的1个客户端时,其他人就不能访问。您也可以为此目的使用信号量。互斥锁是一个"互斥信号量"


4
这并不完全正确。同一线程可以多次进入同一个互斥量,因此需要维护计数以确保进入和退出是平衡的。 - finnw
1
@finnw,一般来说,互斥锁有两种类型,递归和非递归。Java 默认使用递归类型吗? - edA-qa mort-ora-y
2
@edA-qa mort-ora-y,术语“Mutex”在Java VM或API规范中没有使用,因此我假设它是指内置于每个对象中的监视器,这也类似于Win32对象称为Mutex。ReentrantLock也是如此。所有这些都是递归的。我不知道任何非递归互斥量的“真实世界”示例(我只在教科书中看到过它们),因此我没有考虑它们。 - finnw
2
非递归互斥锁可以通过使用计数为一的信号量来实现。如果您想要防止递归调用,这将非常有用。这具有实际用途,我个人在大型项目中使用它来检测初始化代码中的循环(A初始化B,B再试图重新初始化A)。 - Alexander Torstling
1
在C++11(C++0x)标准中,互斥锁(mutex)是非递归的。对于需要递归锁的情况,它们还提供了一个单独的"recursive_mutex"。我知道我们正在谈论Java,但是现在我们许多人都会跨语言编程。 - Aditya Kumar Pandey
显示剩余2条评论

42

Mutex(互斥锁)基本上是一种互斥控制,只有一个线程可以同时获得资源。当一个线程获得资源时,其他线程就不允许获得该资源,直到拥有该资源的线程释放它。所有等待获取资源的线程都将被阻塞。

Semaphore(信号量)用于控制执行的线程数量。有一组固定的资源,每次一个线程占用相同数量的资源时,资源计数会减少。当信号量计数达到0时,不允许其他线程获取资源,这些线程会被阻塞,直到其他占用资源的线程释放它们。

简而言之,主要区别在于“有多少个线程可以同时获取资源?”

  • Mutex -- 只有一个线程。
  • Semaphore -- 定义了一个数量,最多可以同时有这么多个线程获取资源。

11

在IT技术中,mutex用于串行访问资源,而semaphore则限制对资源的访问次数。你可以将mutex视为access count为1的semaphore。无论你将semaphore count设置为多少,都会有多个线程可以访问资源,直到该资源被阻塞。


3
信号量是一种计数同步机制,而互斥锁不是。

3

互斥锁通常被称为二元信号量。虽然信号量可以使用任何非零计数创建,但互斥锁在概念上是具有上限为1的信号量。


2

Semaphore

计数信号量。概念上来说,信号量维护了一组许可证。每个 acquire() 方法都会阻塞,直到有一个许可证可以获得,然后它就会获取该许可证。每个 release() 方法都会释放一个许可证,可能会释放某个正在等待的获取者。但是,实际上没有使用任何许可证对象;Semaphore 只是维护了一个可用数量的计数,并相应地采取行动。

信号量经常用于限制可以访问某些(物理或逻辑)资源的线程数

Java 没有内置的 Mutex API。但是可以将其实现为二进制信号量。

初始化为 1 的信号量,且仅在最多有一个许可证可用时使用,可以用作互斥锁。这更常被称为二进制信号量,因为它只有两种状态:一个许可证可用或零个许可证可用。

当以这种方式使用时,二进制信号量具有以下属性(与许多 Lock 实现不同):“锁”可以由所有者以外的线程释放(因为信号量没有所有权概念)。这在某些专业上下文中可能很有用,例如死锁恢复。

因此,Semaphore 和 Mutex 之间的关键区别

  1. 信号量通过许可证限制了访问资源的线程数。互斥锁只允许一个线程访问资源。

  2. 没有线程拥有 Semaphore。线程可以通过调用 acquire()release() 方法更新许可证数量。Mutex 只应由持有锁的线程解锁。

  3. 当将 Mutex 与条件变量一起使用时,存在隐含的括号——清楚地表明了被保护的程序部分。对于信号量来说,情况并非总是如此,它可能被称为并发编程的 goto —— 它很强大,但过于容易在不确定的结构化方式下使用。


0

你在比较不可比较的东西,从技术上讲,Semaphore和mutex之间没有区别,这是没有意义的。 Mutex只是一个重要的名称,就像应用程序逻辑中的任何名称一样,它意味着你将一个Semaphore初始化为“1”,通常用于保护资源或受保护变量以确保互斥。


0

同步对象Semaphore实现了一个经典的交通信号灯。交通信号灯控制着对由计数器共享的资源的访问。如果计数器大于零,则允许访问;如果为零,则拒绝访问。计数器计算允许访问共享资源的权限。因此,要访问该资源,线程必须从交通信号灯获得许可。一般来说,要使用交通信号灯,想要访问共享资源的线程会尝试获取许可证。如果交通信号灯计数大于零,则线程获取许可证,并且交通信号灯计数减少。否则,线程将被锁定,直到它能够获得许可证。当线程不再需要访问共享资源时,它释放许可证,因此交通信号灯计数增加。如果有另一个线程正在等待许可证,则此时它将获得许可证。Java的Semaphore类实现了这种机制。

Semaphore有两个构造函数:

Semaphore(int num)
Semaphore(int num, boolean come)

num 指定许可的初始计数。然后,num 指定可以同时访问共享资源的线程数。如果 num 为 1,则一次只能访问一个线程的资源。通过将 come 设置为 true,您可以保证等待的线程按照它们请求的顺序被授予权限。


-1

Mutex是二进制信号量。它必须初始化为1,以满足先到先服务的原则。这使我们了解到每个Mutex的另一个特殊属性:执行down操作的人必须执行up操作。因此,我们已经获得了对某些资源的互斥访问。

现在你可以看到,Mutex是一般信号量的特例。


这是不正确的。区别在于所有权。互斥锁只能由当前持有锁的线程解锁。而信号量并非如此。请查看此相关的SO问题:https://dev59.com/DG855IYBdhLWcg3w_5qm - cmhteixeira

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