同步与locking

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

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

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

如果你只是locking一个对象,我宁愿使用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的简单模型和面向范围的本质有助于保护我免受代码中的错误,但是这些相同的优点在更复杂的情况下有时是障碍。 其更复杂的情况是创build并发包来帮助解决。 但是使用这个更高层次的构造需要在代码中更加明确和谨慎的pipe理。

===

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

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

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

虽然同步方法和语句的范围机制使得使用监视器locking进行编程变得更加容易 ,并且有助于避免涉及locking的许多常见编程错误,但是在某些情况下,您需要以更灵活的方式使用locking。 例如,用于遍历并发访问的数据结构的一些algorithm 需要使用“交换”或“链locking” :获得节点A,然后节点B的锁,然后释放A并获取C,然后释放B并获取D等等。 Lock接口的实现允许使用这种技术, 允许在不同的范围 内获取和释放锁 ,并允许以任意顺序获取和释放多个锁

随着这种增加的灵活性,额外的责任 没有块结构的locking会删除同步方法和语句发生的锁的自动释放 。 在大多数情况下,应该使用下面的习语:

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

锁实现通过提供一个非阻塞尝试来获得一个锁(tryLock()),尝试获取可以被中断的锁 (lockInterruptibly()),并尝试获取同步方法和语句可以超时的锁 (tryLock(long,TimeUnit))。

您可以实现java.util.concurrent中的实用程序对低级原语(如synchronizedvolatile或wait / notify

然而,并发性是棘手的,大多数人至less有一些错误的部分,使他们的代码不正确或效率低下(或两者)。

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

Lock类本身在这个工具箱的底层,你可能甚至不需要直接使用它(你可以在大多数情况下使用Queues和Semaphore等等)。

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

注意:同步locking是我说的固有locking的意思。

  1. 当Java 5推出ReentrantLocks时,他们certificate了内在locking具有相当大的吞吐量差异。 如果你正在寻找更快的locking机制,并运行1.5考虑jucReerantrantLock。 Java 6的内在locking现在是可比的。

  2. jucLock有不同的locking机制。 locking可中断 – 试图locking,直到locking螺纹被中断; 定时locking – 尝试locking一段时间,如果不成功则放弃; tryLock – 试图locking,如果其他线程持有锁放弃。 这一切都包括在简单的锁上。 内部locking只提供简单的locking

  3. 样式。 如果1和2都不属于你所关心的类别,那么大部分人(包括我自己)都会发现内部lockingsemenatics更容易阅读,而且更less详细,那么jucocklocking。
  4. 多个条件。 您locking的对象只能通知并等待一个案例。 Lock的newCondition方法允许一个Lock具有多重原因等待或发信号。 实际上我还没有真正需要这个function,但对于那些需要它的人来说,这是一个很好的function。

Brian Goetz的“Java Concurrency In Practice”一书第13.3节:“…和默认的ReentrantLock一样,内部locking不提供确定性的公平性保证,但是大多数locking实现的统计公平性保证足以满足几乎所有的情况…”

我想在Bert F上添加更多的东西。

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

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

从文档页面 locking同步的优点

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

  2. 锁实现通过提供一个非阻塞尝试来获得一个lock (tryLock()) ,尝试获取可以被中断的锁( lockInterruptibly() ,并尝试获取同步方法和语句可以timeout (tryLock(long, TimeUnit))的锁timeout (tryLock(long, TimeUnit))

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

ReentrantLock :根据我的理解, ReentrantLock允许一个对象从一个关键部分重新进入其他关键部分。 由于您已经有锁进入一个关键部分,所以您可以通过使用当前锁来closures同一对象上的其他关键部分。

ReentrantLock按照这篇文章的关键特性

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

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

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

StampedLock:

Java 8提供了一种名为StampedLock的新types锁,它也支持读写锁,就像上面的例子一样。 与ReadWriteLock相反,StampedLock的locking方法返回一个由长整型值表示的图章。

您可以使用这些邮票释放锁或检查锁是否仍然有效。 另外加盖锁支持另一种称为乐观锁的锁模式。

看看这篇关于使用不同types的ReentrantLockStampedLock锁的文章。

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

 synchronized(foo) { } 

要么

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

不保证公平。

如果你有很多的锁的争夺,你可以很容易地遇到新的请求获取锁,更旧的请求卡住。 我见过200个线程在短时间内到达一个锁,第二个到达最后处理的情况。 这对一些应用程序是可以的,但对于其他应用程序则是致命的

请参阅Brian Goetz的“Java并发实践”一书,第13.3节对此主题进行了充分讨论。

锁使程序员的生活更轻松。 这里有一些情况可以通过locking来实现。

  1. 用一种方法locking,用其他方法释放locking。
  2. 你有两个线程在两个不同的代码段上工作,不过第一个线程依赖于第二个线程去完成某段代码,然后继续执行(而其他一些线程也同时工作)。 共享锁可以很容易地解决这个问题。
  3. 实施监视器。 例如,一个简单的队列,put和get方法从许多不同的线程执行。 然而,你是不是想要把相同的方法重叠在一起,也不要把put和get方法重叠在一起。 在这种情况下,私人锁让生活变得更加轻松。

而锁,和条件是build立在同步。 所以当然你可以达到同样的目标。 但是,这可能会使您的生活困难,并可能偏离解决实际问题。

锁和同步之间的主要区别是 – 使用锁,您可以按任意顺序释放和获取锁。 – 与同步,您可以释放锁只有它被收购的顺序。