同步与锁定
java.util.concurrent
API提供了一个名为Lock
的类,该类将基本上序列化控件以访问关键资源。 它提供了诸如park()
和unpark()
。
如果我们可以使用synchronized
关键字并使用wait()
和notify() notifyAll()
方法,我们可以做类似的事情。
我想知道在实践中哪一个更好,为什么?
如果你只是锁定一个对象,我宁愿使用synchronized
例:
Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!
你必须明确地try{} finally{}
到处。
而与同步,这是非常明确的和不可能得到错误的:
synchronized(myObject) {
doSomethingNifty();
}
也就是说, Lock
可能对于更复杂的事情更加有用,因为在这种干净的方式下无法获取和释放它们。 老实说,我宁愿避免首先使用裸露的Lock
,只要符合您的需求,就可以使用更复杂的并发控制,如CyclicBarrier
或LinkedBlockingQueue
。
我从来没有理由使用wait()
或notify()
但可能会有一些好的。
我想知道在实践中哪一个更好,为什么?
我发现Lock
和Condition
(和其他新的concurrent
类)只是工具箱的更多工具。 我可以用我的旧爪锤( synchronized
关键字)完成我所需要的大部分工作,但在某些情况下使用起来很麻烦。 一旦我向我的工具箱中添加更多工具,几个这些尴尬的情况就变得更简单了:橡皮锤,球头锤,撬棒和一些钉子。 不过,我的旧爪锤仍然看到它的使用份额。
我不认为一个人比另一个人“更好”,而是更适合不同的问题。 简而言之, synchronized
的简单模型和范围导向性有助于保护我免受代码中的错误,但这些相同的优势在更复杂的情况下有时是障碍。 其创建并发包的这些更复杂的场景有助于解决这个问题。 但是使用这个更高层次的构造需要在代码中更加明确和谨慎的管理。
===
我认为JavaDoc在描述Lock
和synchronized
之间的区别方面做得很好(重点是我的):
锁定实现提供比使用同步方法和语句可获得的锁定操作更广泛的锁定操作 。 它们允许更灵活的结构化 ,可能具有完全不同的属性,并且可以支持多个关联的Condition对象 。
...
使用同步的方法或语句可以访问与每个对象相关的隐式监视器锁,但是会以块结构的方式强制所有的锁获取和释放 :当获得 多个锁时 ,它们必须以相反的顺序释放 ,并且所有的锁都必须在与它们相同的词汇范围内发布 。
虽然同步方法和语句的范围机制使得使用监视器锁 编程 变得更加容易 ,并且有助于避免涉及锁的许多常见编程错误,但在某些情况下,您需要以更灵活的方式使用锁。 例如,用于遍历并发访问的数据结构的**算法* 需要使用“交换”或“链锁定” :先获得节点A的锁,然后获得节点B的锁,然后释放A并获取C,然后释放B并获取D等等。 Lock接口的实现允许通过允许在不同范围 内获取和释放锁并允许以任何顺序获取和释放多个锁来使用这种技术。
随着这种增加的灵活性,额外的责任 。 没有块结构锁定会删除同步方法和语句发生的锁的自动释放 。 在大多数情况下,应该使用以下习惯用法:
...
当在不同的作用域中发生锁定和解锁时 ,必须注意确保在锁定期间执行的所有代码都由try-finally或try-catch保护,以确保在必要时释放锁定 。
Lock实现通过提供非阻断试图获取锁(的tryLock())提供了对使用的同步方法和语句的附加功能 ,在试图获得 ), 其可以被中断 (的lockInterruptibly( 锁定 ,并在试图获得可以超时的锁 (tryLock(long,TimeUnit))。
...
您可以实现java.util.concurrent中的实用程序对低级基元执行的所有操作,例如synchronized
, volatile
或wait / notify
然而,并发性是棘手的,大多数人至少有一部分错误,使得他们的代码不正确或效率低下(或两者兼而有之)。
并发API提供了更高级的方法,使用起来更容易(并且更安全)。 简而言之,你不需要使用synchronized, volatile, wait, notify
直接synchronized, volatile, wait, notify
。
Lock类本身位于此工具箱的底层,您甚至可能不需要直接使用它(可以在大多数情况下使用Queues
和信号量等等)。