繁体   English   中英

同步与锁定

[英]Synchronization vs Lock

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 ,如果它们满足您的需求,只需使用更复杂的并发控制,例如CyclicBarrierLinkedBlockingQueue

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

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

我发现LockCondition (以及其他新的concurrent类)只是工具箱的更多工具。 我可以用我的旧爪锤( synchronized关键字)完成我需要的大部分事情,但在某些情况下使用起来很尴尬。 一旦我在我的工具箱中添加了更多工具:橡胶锤、球头锤、撬棒和一些钉子冲头,这些尴尬的情况就变得简单多了。 但是,我的旧羊角锤仍然可以使用。

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

===

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

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

...

同步方法或语句的使用提供了对与每个对象关联的隐式监视器锁的访问,但强制所有锁的获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序释放,并且所有锁都必须在它们被获取的同一个词法范围内释放

虽然同步方法和语句的作用域机制使使用监视器锁编程变得更加容易,并有助于避免许多涉及锁的常见编程错误,但在某些情况下,您需要以更灵活的方式使用锁。 例如,用于遍历并发访问的数据结构的* *某些算法*需要使用“交手”或“链锁” :您先获取节点 A 的锁,然后是节点 B,然后释放 A 并获取 C,然后释放 B 并获得 D,依此类推。 Lock 接口的实现通过允许在不同范围内获取和释放锁,并允许以任何顺序获取和释放多个锁来启用此类技术。

增加的灵活性带来了额外的责任 块结构锁缺失消除了同步方法和语句中发生的锁的自动释放 在大多数情况下,应使用以下习语:

...

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

Lock实现通过提供非阻断试图获取锁提供比使用的同步方法和语句的附加功能(的tryLock()),在试图获得),其可以被中断(的lockInterruptibly(锁定,并在试图获得可以超时的锁(tryLock(long, TimeUnit))。

...

您可以使用诸如synchronizedvolatilewait / notify 之类的低级原语来实现java.util.concurrent 中的实用程序所做的一切

然而,并发是棘手的,大多数人至少会出错,导致他们的代码不正确或效率低下(或两者兼而有之)。

并发 API 提供了一种更高级别的方法,使用起来更容易(因此更安全)。 简而言之,您不再需要直接使用synchronized, volatile, wait, notify

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

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

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

  1. 当 Java 5 推出 ReentrantLocks 时,事实证明它们在吞吐量上与内在锁有相当明显的差异。 如果您正在寻找更快的锁定机制并且正在运行 1.5,请考虑 jucReentrantLock。 Java 6 的内在锁定现在具有可比性。

  2. jucLock 有不同的锁定机制。 Lock interruptable - 尝试锁定直到锁定线程被中断; 定时锁定 - 尝试锁定一定时间,如果不成功则放弃; tryLock - 尝试锁定,如果其他线程持有锁定放弃。 除了简单的锁之外,这一切都包括在内。 内在锁只提供简单的锁

  3. 风格。 如果 1 和 2 都不属于您所关心的类别,包括我自己在内的大多数人会发现内在锁定语义比 jucLock 锁定更易于阅读且不那么冗长。
  4. 多重条件。 您锁定的对象只能收到通知并等待单个案例。 Lock 的 newCondition 方法允许单个 Lock 有多种原因等待或发出信号。 在实践中我还没有真正需要这个功能,但对于那些需要它的人来说,这是一个很好的功能。

我想在Bert F答案之上添加更多内容。

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

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

从文档页面锁定优于同步的优点

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

  2. 锁实现通过提供非阻塞的获取lock (tryLock())尝试lock (tryLock()) 、获取可以被中断的锁的尝试 ( lockInterruptibly()和尝试获取可以timeout (tryLock(long, TimeUnit))的锁timeout (tryLock(long, TimeUnit))

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

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

根据本文的ReentrantLock关键功能

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

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

除了这三个 ReentrantLocks,java 8 还提供了一个 Lock

冲压锁:

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

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

看看这篇关于不同类型ReentrantLockStampedLock锁用法的文章

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

synchronized(foo) {
}

或者

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

不保证公平。

如果您对锁有很多争用,您很容易遇到闯入,其中新请求获得锁而旧请求卡住。 我见过有 200 个线程在短时间内到达以获取锁而第 2 个到达的线程最后被处理的情况。 这对于某些应用程序来说没问题,但对于其他应用程序来说却是致命的。

有关此主题的完整讨论,请参阅 Brian Goetz 的“Java Concurrency In Practice”一书的 13.3 节。

Brian Goetz 的“Java Concurrency In Practice”一书,第 13.3 节:“……与默认的 ReentrantLock 一样,内在锁定不提供确定性的公平性保证,但大多数锁定实现的统计公平性保证对于几乎所有情况都足够好……”

Lock 让程序员的生活更轻松。 以下是一些可以通过锁定轻松实现的情况。

  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 可用于防止不必要的块锁定。

java.util.concurrent API提供了一个称为Lock的类,该类将基本上对控件进行序列化以访问关键资源。 它给出了诸如park()unpark()

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM