同步与锁定

java.util.concurrent API 提供了一个称为 Lock的类,它基本上是序列化控件以访问关键资源。给出了 park()unpark()等方法。

我们可以做类似的事情,如果我们可以使用 synchronized关键字和使用 wait()notify() notifyAll()方法。

我想知道哪一个在实践中更好,为什么?

181132 次浏览

如果你只是锁定一个对象,我更喜欢使用synchronized

例子:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

你必须在任何地方显式地执行try{} finally{}

而对于synchronized,它非常清楚,不可能出错:

synchronized(myObject) {
doSomethingNifty();
}

也就是说,__abc0可能对更复杂的东西更有用,在这些地方你不能以这样干净的方式获取和释放。老实说,我更倾向于在一开始就避免使用纯粹的__abc0,而只是使用更复杂的并发控制,如CyclicBarrierLinkedBlockingQueue,如果它们满足你的需求的话。

我从来没有理由使用wait()notify(),但可能有一些好的理由。

你可以用底层原语如synchronizedvolatile等待 / 通知来实现java . util . concurrent中的实用程序所做的一切

然而,并发性是棘手的,大多数人至少在某些方面犯了错误,导致他们的代码要么不正确,要么效率低下(或两者兼而有之)。

并发API提供了更高级的方法,使用起来更容易(因此也更安全)。简而言之,你不应该再直接使用synchronized, volatile, wait, notify了。

类本身在这个工具箱的底层,你甚至可能不需要直接使用它(你可以使用Queues信号量等等,大多数时候)。

我想知道哪一个在实践中更好,为什么?

我发现LockCondition(和其他新的concurrent类)只是工具箱的更多工具。我可以用我的旧羊角锤(synchronized关键字)做几乎所有我需要的事情,但在某些情况下使用它很尴尬。在我的工具箱里增加了一些工具后,一些尴尬的情况变得简单多了:一个橡胶槌,一个圆头锤,一根撬棍和一些钉子冲子。然而,我的旧羊角锤仍然看到它的使用份额。

我不认为其中一个真的比另一个“更好”,而是每个都更适合不同的问题。简而言之,synchronized的简单模型和面向作用域的性质有助于保护我免受代码中的错误,但在更复杂的场景中,这些相同的优点有时会成为障碍。并发包的创建就是为了帮助解决这些更复杂的场景。但是使用这种高级结构需要在代码中进行更明确和更仔细的管理。

= = =

我认为JavaDoc很好地描述了Locksynchronized之间的区别(重点是我的):

锁实现提供了可以通过同步方法和语句获得的更广泛的锁定操作。它们允许更灵活的结构,可能具有完全不同的属性,并且可能支持多个关联条件对象

...

< em > < / em >同步方法或语句的使用提供了对与每个对象关联的隐式监视锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当多个锁< em > < / em >< em > < / em >收购时,它们为必须以相反的顺序释放,并且所有锁必须在获得它们的相同词法作用域中释放

< em > < / em >同步方法和语句的作用域机制使监视器锁编程更容易,并帮助避免许多涉及锁的常见编程错误,在某些情况下,您需要以更灵活的方式使用锁。例如:**某些算法*遍历并发访问的数据结构需要使用“手拉手”或“链锁”:您获得节点A的锁,然后是节点B,然后释放A并获得C,然后释放B并获得D,等等。< em >锁定界面< / em >的实现允许通过允许在不同范围内获取和释放锁允许以任意顺序获取和释放多个锁使用这些技术。

用这个增加的灵活性意味着更多的责任。与同步方法和语句一起出现的缺少块结构锁将删除锁的自动释放。在大多数情况下,应该使用以下成语:

...

锁定和解锁发生在不同的作用域时,必须注意< em >确保< / em >,在必要时,在持有锁时执行的所有代码被try-finally或try-catch保护确保锁被释放

锁实现通过使用同步方法和语句提供非阻塞尝试获取锁(tryLock()),尝试获取可以被中断的锁 (lockInterruptibly()和尝试获取可以超时的锁 . (tryLock(long, TimeUnit))来提供< / em > < em >附加功能

...

为什么要使用synchronizedjava.util.concurrent.Lock有4个主要因素。

注意:同步锁定就是我所说的内在锁定。

  1. 当Java 5问世时 ReentrantLocks,他们证明了 相当可观的吞吐量 差异和内在锁定。 如果你正在寻找更快的锁定 机制和运行1.5 考虑j.u.c.ReentrantLock。Java 6的内在锁定现在是 李可比性。< / p > < / > <李> < p > j.u.c。锁有不同的机制 锁定。锁可中断- 尝试锁定直到锁定 线程中断;定时锁定- 尝试锁定一定的数量 时间和放弃,如果你没有 成功;tryLock -尝试锁定, 如果其他线程持有 锁死。这些都包括在内 除了简单的锁。 内在锁定只提供简单的 李锁定< / p > < / > <李>风格。如果1和2都不落 按你是什么来分类 关心大多数人, 包括我自己,都会发现 更容易实现内在锁语义 读起来更少啰嗦 李j.u.c.Lock锁定。< / > <李>多个条件。一个你的对象 锁定只能被通知 只等了一个案子。锁的 newCondition方法允许 单锁有多重原因 等待:等待或发出信号我还没有 实际上需要这个功能 这是一个很好的特性 那些需要它的人。

主要的区别是公平性,换句话说,请求是FIFO处理还是可以有驳船?方法级同步确保公平或FIFO分配锁。使用

synchronized(foo) {
}

lock.acquire(); .....lock.release();

不能保证公平。

如果您对锁有很多争用,那么您很容易遇到barging,即新请求获得锁而旧请求卡住。我曾经见过这样的情况:为了一个锁,200个线程在短时间内到达,而第二个到达的线程最后被处理。这对于某些应用程序是可行的,但对于其他应用程序则是致命的。

请参阅Brian Goetz的《Java并发实践》一书中的13.3节,以获得关于此主题的完整讨论。

Brian Goetz的“Java并发实践”一书,第13.3节: "...像默认的ReentrantLock一样,内在锁不提供确定性的公平性保证,但是 大多数锁定实现的统计公平性保证对于几乎所有情况都足够好……" < / p >

我想在伯特F answer上添加更多的东西。

Locks支持各种细粒度锁控制方法,这些方法比隐式监控器更具表现力(synchronized锁)

Lock提供对共享资源的独占访问:一次只有一个线程可以获得锁,并且对共享资源的所有访问都要求首先获得锁。但是,有些锁可能允许对共享资源的并发访问,例如ReadWriteLock的读锁。

同步锁定的优点来自文档页面

  1. 同步方法或语句的使用提供了对与每个对象关联的隐式监控器锁的访问,但强制所有锁的获取和释放都以块结构的方式进行

  2. 锁实现在同步方法和语句的使用中提供了额外的功能,通过提供非阻塞尝试获取lock (tryLock()),尝试获取可以中断的锁(lockInterruptibly()),以及尝试获取可以timeout (tryLock(long, TimeUnit))的锁。

  3. Lock类还可以提供与隐式监控器锁完全不同的行为和语义,例如保证排序、不可重入使用或死锁检测

ReentrantLock:简单来说,根据我的理解,ReentrantLock允许对象从一个临界区重新进入到另一个临界区。由于您已经锁定进入一个临界区,您可以使用当前锁定进入同一对象的其他临界区。

根据此文章 . ReentrantLock关键特性

  1. 能够可中断地锁定。
  2. 能力超时,而等待锁定。
  3. 创造公平锁的权力。
  4. API来获取等待锁的线程列表。
  5. 灵活地尝试锁定而不阻塞。

你可以使用ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock来进一步获得对读写操作的粒度锁的控制。

除了这三个reentrantlock之外,java 8还提供了另外一个Lock

StampedLock:

Java 8附带了一种名为StampedLock的新锁,它也支持读写锁,就像上面的例子一样。与ReadWriteLock相反,StampedLock的锁定方法返回一个由长值表示的戳。

您可以使用这些戳记来释放锁或检查锁是否仍然有效。此外,戳锁支持另一种锁模式,称为乐观锁。

看看这个文章对不同类型的ReentrantLockStampedLock锁的使用情况。

锁让程序员的生活更轻松。以下是使用锁可以轻松实现的几种情况。

  1. 在一个方法中锁定,并在另一个方法中释放锁。
  2. 如果你有两个线程处理两个不同的代码段,但是,在第一个线程中有一个先决条件,在第二个线程中有一个特定的代码段(而其他一些线程也在第二个线程中同时处理同一段代码)。共享锁可以很容易地解决这个问题。
  3. 实施监控。例如,一个简单的队列,其中put和get方法从许多其他线程执行。但是,您不希望同时运行多个put(或get)方法,也不希望同时运行put和get方法。私有锁使您更容易实现这一点。

同时,锁和条件建立在同步机制之上。因此,当然可以实现与使用锁相同的功能。但是,使用同步解决复杂的场景可能会使您的生活变得困难,并可能使您偏离解决实际问题的方向。

锁和同步的主要区别:

  • 使用锁,您可以以任何顺序释放和获取锁。
  • 使用synchronized,您只能按照获取锁的顺序释放锁。

锁和同步块都有相同的目的,但这取决于使用情况。考虑以下部分

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}


.
.
.
synchronize(this)
{
// do some functionality
}




} // end of randomFunction

在上述情况下,如果一个线程进入同步块,另一个块也被锁定。如果在同一个对象上有多个这样的同步块,则所有的同步块都被锁定。在这种情况下,可以使用java.util.concurrent.Lock来防止不必要的块锁定