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