java.util.concurrent
API提供了一个名为Lock
的类,它基本上会对控件进行序列化以访问关键资源。它提供了park()
和unpark()
等方法。
如果我们可以使用synchronized
关键字并使用wait()
和notify() notifyAll()
方法,我们可以执行类似的操作。
我想知道其中哪一个在实践中更好,为什么?
答案 0 :(得分:170)
如果你只是锁定一个对象,我宁愿使用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()
,但可能会有一些好的。
答案 1 :(得分:62)
我想知道其中哪一个在实践中更好,为什么?
我发现Lock
和Condition
(以及其他新的concurrent
类)只是工具箱的更多工具。我可以使用旧的羊角锤(synchronized
关键字)完成我需要的大部分工作,但在某些情况下使用它很尴尬。一旦我在工具箱中添加了更多工具,一些笨拙的情况变得更加简单:橡皮锤,圆头锤,撬棒和一些钉子。 然而,我的旧羊角锤仍然可以使用它。
我不认为一个人真的比其他人“更好”,而是每个人都更适合不同的问题。简而言之,synchronized
的简单模型和面向范围的特性有助于保护我免受代码中的错误的影响,但这些相同的优点有时会成为更复杂场景中的障碍。这些更复杂的场景是创建并发包以帮助解决的问题。但是使用这种更高级别的结构需要在代码中进行更明确和仔细的管理。
===
我认为JavaDoc很好地描述了Lock
和synchronized
之间的区别(重点是我的):
锁实现提供 更广泛的锁定操作 ,而不是使用同步方法和语句。它们允许 更灵活的结构 ,可能具有完全不同的属性,并且可能 支持多个关联的条件对象 。
...
使用 同步方法 或语句可以访问与每个对象关联的隐式监视器锁,但 强制所有锁获取和释放到以块结构的方式发生 :当 多个锁 获得 时 必须以相反的顺序发布 ,并且所有 锁必须在获取它们的相同词法范围内发布 强>
虽然 同步 方法和语句 的范围机制使得使用监视器锁定 更加容易,并帮助 避免许多涉及锁的常见编程错误,有时您需要以更灵活的方式使用锁。例如,* *某些算法* 用于遍历并发访问的数据结构 需要使用“hand-hand-hand”或“chain locking” :获取节点A的锁,然后获取节点B,然后释放A并获取C,然后释放B并获取D,依此类推。 锁接口 的实现允许 使用此类技术,允许在不同范围内获取和发布锁 和 允许以任何顺序获取和发布多个锁 。
通过此 增加灵活性带来额外责任 。 缺少块结构锁定会删除使用同步方法和语句发生的锁 的自动释放。在大多数情况下,应使用以下习语:
...
当 锁定和解锁发生在不同的范围 时,必须注意 确保 所有代码锁定时执行 受try-finally保护或尝试捕获 确保锁定已释放 强大>必要时。
通过提供 非阻塞尝试获取 <,锁实现提供 附加功能 ,而不是使用同步方法和语句/ strong>锁定(tryLock()),尝试 获取可能被中断的锁 (lockInterruptibly(),并尝试 获取可以超时的锁 (tryLock(long,TimeUnit))。
...
答案 2 :(得分:20)
您可以使用synchronized
,volatile
或java.util.concurrent / wait
但是,并发性很棘手,并且大多数人至少将其中的某些部分弄错了,使得他们的代码不正确或效率低下(或两者兼而有之)。
并发API提供了更高级别的方法,使用起来更容易(并且更安全)。简而言之,您不应再需要直接使用synchronized, volatile, wait, notify
了。
Lock类本身位于此工具箱的较低级别,您甚至可能不需要直接使用它(您可以使用Queues
和Semaphore和东西,等,大部分时间)。
答案 3 :(得分:13)
您希望使用synchronized
或java.util.concurrent.Lock
的原因有四个主要因素。
注意:当我说内部锁定时,同步锁定就是我的意思。
当Java 5推出时 ReentrantLocks,他们证明了 非常明显的吞吐量 差异然后内在锁定。 如果你正在寻找更快的锁定 机制并正在运行1.5 考虑j.u.c.ReentrantLock。 Java的 6的内在锁定现在 可比性。
j.u.c.Lock有不同的机制 用于锁定。锁可中断 - 试图锁定直到锁定 线程中断;定时锁定 - 试图锁定一定数量 时间,如果你不这样做就放弃 成功; tryLock - 尝试锁定, 如果其他一些线程持有 锁放弃。这一切都包括在内 除了简单的锁定。 内在锁定只提供简单 锁定
答案 4 :(得分:5)
我想在 Bert F 答案之上添加更多内容。
Locks
支持更精细粒度锁控制的各种方法,这些方法比隐式监视器更具表现力(synchronized
锁定)
Lock提供对共享资源的独占访问:一次只有一个线程可以获取锁,并且对共享资源的所有访问都需要首先获取锁。但是,某些锁可能允许并发访问共享资源,例如ReadWriteLock的读锁定。
文档page
中锁定同步的优点使用同步方法或语句可以访问与每个对象关联的隐式监视器锁,但强制所有锁获取和释放以块结构方式发生
通过提供获取lock (tryLock())
的非阻塞尝试,尝试获取可被中断的锁(lockInterruptibly()
,锁实现提供了使用同步方法和语句的附加功能。并尝试获取可以timeout (tryLock(long, TimeUnit))
。
Lock类还可以提供与隐式监视器锁完全不同的行为和语义,例如保证排序,非重入使用或死锁检测
ReentrantLock:根据我的理解,简单来说,ReentrantLock
允许对象从一个关键部分重新进入其他关键部分。由于您已经锁定以输入一个关键部分,因此您可以使用当前锁定在同一对象上的其他关键部分。
ReentrantLock
根据此article
您可以使用ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
进一步获取对读写操作的粒度锁定的控制。
除了这三个ReentrantLocks之外,java 8还提供了一个Lock
<强> StampedLock:强>
Java 8附带了一种名为StampedLock的新锁,它也支持读写锁,就像上面的例子一样。与ReadWriteLock相比,StampedLock的锁定方法返回由long值表示的戳记。
您可以使用这些标记释放锁定或检查锁定是否仍然有效。另外,标记锁支持另一种称为乐观锁定的锁定模式。
关于使用不同类型的ReentrantLock
和StampedLock
锁,请查看此article。
答案 5 :(得分:4)
主要区别在于公平性,换句话说是请求处理FIFO还是存在趸论?方法级同步确保锁的公平或FIFO分配。使用
synchronized(foo) {
}
或
lock.acquire(); .....lock.release();
不保证公平。
如果您对锁定存在很多争用,则很容易遇到更新请求获取锁定且旧请求被卡住的情况。我已经看到过200个线程在短时间内到达锁定而第二个到达的情况最后被处理的情况。对于某些应用程序来说这是可以的,但对于其他应用程序来说这是致命的。
请参阅Brian Goetz的“Java Concurrency In Practice”一书,第13.3节,对该主题进行全面讨论。
答案 6 :(得分:3)
Brian Goetz的“Java Concurrency In Practice”一书,第13.3节: “...与默认的ReentrantLock一样,内在锁定不提供确定性的公平性保证,但是 大多数锁定实现的统计公平性保证对于几乎所有情况都足够好......“
答案 7 :(得分:1)
Lock使程序员的生活更轻松。以下几种情况可以通过锁定更容易实现。
同时,锁和条件构建在synchronized上。所以你当然可以实现同样的目标。但是,这可能会使您的生活变得困难,并可能使您无法解决实际问题。
答案 8 :(得分:0)
锁定和同步之间的主要区别:
答案 9 :(得分:0)
锁定和同步块的作用相同,但这取决于用法。考虑下面的部分
void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}
.
.
.
synchronize(this)
{
// do some functionality
}
} // end of randomFunction
在上述情况下,如果线程进入同步块,则另一个块也被锁定。如果同一对象上有多个这样的同步块,则所有块均被锁定。在这种情况下,可以使用java.util.concurrent.Lock防止不必要的块锁定
答案 10 :(得分:0)
这里的许多答案建议使用同步。
但是,这取决于用例。
synced关键字自然具有内置的语言支持。这可能意味着JIT可以使用锁无法实现的方式优化同步块。例如它可以组合同步块。 使用同步块,在任何给定时间点仅允许一个线程访问一种方法。这是一个非常昂贵的操作。
锁通过允许出于不同目的配置各种锁来避免这种情况。一个锁下可以同步几个方法,而另一个锁下可以同步其他方法。这样可以提高并发性,并提高整体性能。
因此,对于一个较小的系统,该系统可以不进行并发操作并允许一个线程执行操作,则同步可以正常工作。否则,可以锁定键。