与信号量或监视器同步更好吗?
答案 0 :(得分:14)
“更好”取决于背景。根据詹姆斯麦克帕兰的说法,他们“同样强大”。我强烈建议您查看his blog for a discussion on the differences。
以下是我发现的快速指南:
<强>信号灯强>
Wait()
并不总是阻止调用者(即,当信号量计数器大于零时)。Signal()
要么释放被阻止的线程,要么增加信号量计数器。Signal()
释放被阻止的线程,则调用方和已发布的线程都会继续。条件变量
Wait()
始终阻止来电者。Signal()
要么释放一个被阻止的线程,如果有一个,要么信号丢失,好像它永远不会发生。Signal()
释放被阻止的线程,则调用者将生成监视器(Hoare类型)或继续(Mesa类型)。只有一个调用者或已释放的线程可以继续,但不能同时继续。此信息来自:http://www.cs.mtu.edu/~shene/NSF-3/e-Book/MONITOR/sema-vs-monitor.html
一些有用的资源:
答案 1 :(得分:8)
如果您在头痛最小化之后,更喜欢监视器(synchronized
块/方法)而不是您认为真正选择锁定原语的信号量。
忽略大学关于信号量灵活性的讨论。你是在追求可靠性,而不是可配置性,不是吗?
通常声称监视器和信号量是等效的(可以相互模拟),但这种等效性比sometimes expected更抽象,更有帮助。 任何能够正确模拟其他人的人都不再需要这个问题的答案了。
显然,在允许同时进入块的线程数大于1的情况下,信号量是唯一可行的选择。因此,监视器的真正竞争对手是二进制信号量,即那些初始化为1的数字,另外只有那些你希望锁定相同的线程最终解锁信号量的那些。所以让我们仔细看看那些情况。
监视器和二进制信号量之间的根本区别在于线程所有权。它有很大的影响,那就是重入的能力。可重入意味着已经拥有锁的线程可以再次获取它而不是死锁。如果你的类已经共享状态,你只想在所有方法中保护但是无法承担关于这些方法如何相互调用的永久假设,那么这是一个大问题;或者通常在您的线程安全方案中发展的任何皮带和支撑特征。
信号量永远不可重入,Java监视器始终是可重入的。如果需要从多个代码位置锁定同一个对象,semaphores are prone to deadlocks即使在单线程场景中也是如此 - 以及信号量的这种限制只有在相对罕见的情况下才能带来任何好处,而监控器实际上并不是一种选择。
线程所有权还会大大降低忘记锁定,忘记解锁或一个线程的活动屏蔽另一个线程的活动的风险。相关的Java语法也存在显着的人体工程学差异。
另请注意this question;虽然它使用了不同的术语,但更好的答案是将“互斥”理解为Java“监视器”。
答案 2 :(得分:3)
要通过监视器进行确认,我们指的是旧的synchronized
关键字。
第一个问题,你需要锁才能有一个计数器吗?
J2SE 5.0 concurrency article在这里给出了很好的建议。监视器有限,因为:
因此,如果这些项目中的任何一项对您很重要 - 在超时后退是一个很好的例子 - 那么请使用信号量。如果没有,显示器就可以了。
答案 3 :(得分:2)
首先,您必须决定使用哪种JDK。 第一个只发布Threads的Java版本。自Java Tiger(5.0)以来,引入了新类来处理并发。 特别是,提供了一个完整的包, java.util.concurrent 。
根据我的经验,我发现显示器更好,因为它们让代码更干净。而且,使用它们可以使代码更容易理解。它们通常通过实现 Lock 接口的类来实现:JDK提供的最着名的实现是 ReentrantLock 类,它定义了一般锁定,而 ReentrantReadWriteLock 类,提供特定的写和/或读锁。
因此,锁用于启用/禁用对共享对象(例如对象列表)的访问。
信号量对象是用于协调和控制线程的同步(最新JDK中提供了许多同步器,如信号量, CyclicBarrier , CountdownLatch 和 Exchanger 类)。 例如,使用信号量,您可以向线程池释放固定数量的令牌,从而决定可以同时执行的操作量。就个人而言,我不喜欢这种方法,因为使用Futures和Locks的线程池会以更清洁,更安全的方式产生相同的结果。
可以在本书中找到更多信息:“Java Concurrency in Practice”以及此IBM教程:“Concurrency in JDK 5.0”。可以找到一些更好的例子here。