同步与锁定

day*_*mer 173 java concurrency multithreading synchronization java.util.concurrent

java.util.concurrentAPI提供了一个名为as的类Lock,它基本上将序列化控件以访问关键资源.它给出了诸如park()和的方法unpark().

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

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

Ste*_*ker 173

如果您只是锁定一个对象,我宁愿使用 synchronized

例:

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

你必须明确地try{} finally{}到处做.

鉴于同步,它是非常明确的,不可能出错:

synchronized(myObject) {
    doSomethingNifty();
}
Run Code Online (Sandbox Code Playgroud)

也就是说,Lock对于那些你无法以如此干净的方式获取和释放的更复杂的东西,s可能更有用.老实说,我希望首先避免使用裸机,如果满足您的需求Lock,只需使用更复杂的并发控制,如a CyclicBarrier或a LinkedBlockingQueue.

我从来没有理由使用wait()或者notify()可能有一些好的.

  • 起初这个例子对锁有意义,但后来我意识到,如果你使用try finally块,那么在没有释放锁的情况下可以避免这个问题 (5认同)

Ber*_*t F 64

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

我发现LockCondition(以及其他新concurrent类)只是工具箱的更多工具.我可以用我的旧羊角锤(synchronized关键字)完成我需要的大部分工作,但在某些情况下使用它很尴尬.一旦我在工具箱中添加了更多工具,一些笨拙的情况变得更加简单:橡皮锤,圆头锤,撬棒和一些钉子.然而,我的旧羊角锤仍然看到它的使用份额.

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

===

我认为JavaDoc中确实描述之间的区别的一个好工作Locksynchronized(重点是我的):

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

...

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

虽然同步方法和语句的作用域机制使得使用监视器锁进行编程变得更加容易,并且有助于避免许多涉及锁的常见编程错误,但有时您需要以更灵活的方式使用锁.例如,**用于遍历并发访问的数据结构的某些算法*需要使用"hand-hand-hand"或"chain locking":获取节点A的锁,然后获取节点B,然后释放A并获取C,然后释放B并获得D等等.所述的实施方式中锁定接口通过使得能够使用这样的技术允许获得并在不同的范围释放锁,并允许以任何顺序获取和释放多个锁.

随着这种增加的灵活性,附加责任.在不存在块结构锁定的去除锁的自动释放,与同步方法和语句发生.在大多数情况下,应使用以下习语:

...

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

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

...


Thi*_*ilo 22

您可以实现一切在公用事业java.util.concurrent中 做的低级原像synchronized,volatile等待/通知

但是,并发性很棘手,并且大多数人至少会对其中的某些部分进行错误处理,从而导致其代码不正确或效率低下(或两者兼而有之).

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

类本身就是对这个工具箱的下级侧,你甚至可能不会需要使用直接或者(你可以用Queues信号量和材料等,大部分的时间).

  • 是否将普通的旧等待/通知视为比java.util.concurrent.locks.LockSupport的park / unpark更低的基元,或者相反? (2认同)
  • 不,我不是说3:Thread.sleep / interrupt,Object.wait / notify,LockSupport.park / unpark,这是最“ **”的原语吗? (2认同)
  • @Thilo我不确定你是如何支持你的声明`java.util.concurrent`比语言特性(`synchronized`等等)更容易[一般].当你使用`java.util.concurrent`时,你必须习惯完成`lock.lock(); 在编写代码之前尝试{...} finally {lock.unlock()}`而使用`synchronized`你从一开始就基本上没问题.仅在此基础上,我会说`synchronized`比`java.util.concurrent.locks.Lock`更容易(假设你想要它的行为).[par 4](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html) (2认同)

Joh*_*int 14

有四个主要因素可以解释为什么要使用synchronizedjava.util.concurrent.Lock.

注意:当我说内部锁定时,同步锁定就是我的意思.

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

  2. jucLock具有不同的锁定机制.锁定可中断 - 尝试锁定直到锁定螺纹中断; 定时锁定 - 试图锁定一段时间,如果不成功则放弃; tryLock - 尝试锁定,如果某个其他线程持有锁放弃.这一切都包含在简单的锁定之外.内在锁定仅提供简单锁定

  3. 样式.如果1和2都不属于你所关注的大多数人的类别,包括我自己,会发现内在的锁定semenatics更容易阅读,而且jucLock锁定更简洁.
  4. 多个条件.您锁定的对象只能被通知并等待一个案例.Lock的newCondition方法允许单个Lock有等待或发出信号的多个原因.我在实践中还没有真正需要这个功能,但对于那些需要它的人来说这是一个很好的功能.


Bri*_*box 6

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

synchronized(foo) {
}
Run Code Online (Sandbox Code Playgroud)

或者

lock.acquire(); .....lock.release();
Run Code Online (Sandbox Code Playgroud)

不保证公平。

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

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

  • “方法级同步确保锁的公平或先进先出分配。” => 真的吗?您是说同步方法的行为与将方法内容包装到同步{} 块中的公平性不同吗?我不会这么认为,还是我理解错了那句话……? (5认同)

小智 6

锁和同步的主要区别:

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


Rav*_*abu 5

我想在Bert F回答之上添加更多内容 .

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

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

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

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

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

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

ReentrantLock:根据我的理解,简单来说,ReentrantLock允许对象从一个关键部分重新进入其他关键部分.由于您已经锁定以输入一个关键部分,因此可以使用当前锁定在同一对象上的其他关键部分.

ReentrantLock主要特点为每本文章

  1. 能够无故锁定.
  2. 能够在等待锁定时超时.
  3. 创造公平锁定的力量.
  4. 用于获取锁定等待线程列表的A​​PI.
  5. 灵活地尝试锁定而不会阻塞.

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

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

StampedLock:

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

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

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