同步与锁定

时间:2010-11-17 05:13:01

标签: java multithreading concurrency synchronization java.util.concurrent

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

如果我们可以使用synchronized关键字并使用wait()notify() notifyAll()方法,我们可以执行类似的操作。

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

11 个答案:

答案 0 :(得分:170)

如果你只是锁定一个对象,我宁愿使用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(),但可能会有一些好的。

答案 1 :(得分:62)

  

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

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

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

===

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

  

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

     

...

     

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

     

虽然 同步 方法和语句 的范围机制使得使用监视器锁定 更加容易,并帮助 避免许多涉及锁的常见编程错误,有时您需要以更灵活的方式使用锁。例如,* *某些算法* 用于遍历并发访问的数据结构 需要使用“hand-hand-hand”或“chain locking” :获取节点A的锁,然后获取节点B,然后释放A并获取C,然后释放B并获取D,依此类推。 锁接口 的实现允许 使用此类技术,允许在不同范围内获取和发布锁 允许以任何顺序获取和发布多个锁

     

通过此 增加灵活性带来额外责任 缺少块结构锁定会删除使用同步方法和语句发生的锁 的自动释放。在大多数情况下,应使用以下习语:

     

...

     

锁定和解锁发生在不同的范围 时,必须注意 确保 所有代码锁定时执行 受try-finally保护或尝试捕获 确保锁定已释放 必要时。

     

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

     

...

答案 2 :(得分:20)

您可以使用synchronizedvolatilejava.util.concurrent / wait notify中的所有实用程序>

但是,并发性很棘手,并且大多数人至少将其中的某些部分弄错了,使得他们的代码不正确或效率低下(或两者兼而有之)。

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

Lock类本身位于此工具箱的较低级别,您甚至可能不需要直接使用它(您可以使用QueuesSemaphore和东西,等,大部分时间)。

答案 3 :(得分:13)

您希望使用synchronizedjava.util.concurrent.Lock的原因有四个主要因素。

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

  1. 当Java 5推出时 ReentrantLocks,他们证明了 非常明显的吞吐量 差异然后内在锁定。 如果你正在寻找更快的锁定 机制并正在运行1.5 考虑j.u.c.ReentrantLock。 Java的 6的内在锁定现在 可比性。

  2. j.u.c.Lock有不同的机制     用于锁定。锁可中断 -     试图锁定直到锁定     线程中断;定时锁定 -     试图锁定一定数量     时间,如果你不这样做就放弃     成功; tryLock - 尝试锁定,     如果其他一些线程持有     锁放弃。这一切都包括在内     除了简单的锁定。     内在锁定只提供简单     锁定

  3. 样式。如果1和2都没有下降     你是什​​么类别     关心大多数人,     包括我在内,会找到的     内在锁定更容易     阅读,然后再详细     j.u.c.Lock locking。
  4. 多个条件。一个对象你 锁定只能通知和 等待一个案子。锁定的 newCondition方法允许a 单锁具有多重原因 等待或发信号我还没有 实际上需要这个功能 练习,但是一个很好的功能 那些需要它的人。

答案 4 :(得分:5)

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

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

  

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

文档page

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

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

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

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

    ReentrantLock根据此article

    的主要功能
    1. 能够无阻地锁定。
    2. 等待锁定时超时的能力。
    3. 创造公平锁定的力量。
    4. 获取锁定等待线程列表的A​​PI。
    5. 灵活地尝试锁定而不会阻塞。
    6. 您可以使用ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock进一步获取对读写操作的粒度锁定的控制。

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

      <强> StampedLock:

        

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

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

      关于使用不同类型的ReentrantLockStampedLock锁,请查看此article

答案 5 :(得分:4)

主要区别在于公平性,换句话说是请求处理FIFO还是存在趸论?方法级同步确保锁的公平或FIFO分配。使用

synchronized(foo) {
}

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

不保证公平。

如果您对锁定存在很多争用,则很容易遇到更新请求获取锁定且旧请求被卡住的情况。我已经看到过200个线程在短时间内到达锁定而第二个到达的情况最后被处理的情况。对于某些应用程序来说这是可以的,但对于其他应用程序来说这是致命的。

请参阅Brian Goetz的“Java Concurrency In Practice”一书,第13.3节,对该主题进行全面讨论。

答案 6 :(得分:3)

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

答案 7 :(得分:1)

Lock使程序员的生活更轻松。以下几种情况可以通过锁定更容易实现。

  1. 锁定一个方法,并以其他方法释放锁定。
  2. 你有两个线程处理两个不同的代码片段,但是第一个线程依赖于第二个线程来完成某些代码片段,然后再继续进行(而其他一些线程也同时工作)。共享锁可以很容易地解决这个问题。
  3. 实施监视器。例如,一个简单的队列,其中put和get方法从许多不同的线程执行。但是,你是否想要在一个又一个上使用相同的方法,并且put和get方法都不能重叠。在这种情况下,私人锁可以让生活变得更轻松。
  4. 同时,锁和条件构建在synchronized上。所以你当然可以实现同样的目标。但是,这可能会使您的生活变得困难,并可能使您无法解决实际问题。

答案 8 :(得分:0)

锁定和同步之间的主要区别:

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

答案 9 :(得分:0)

锁定和同步块的作用相同,但这取决于用法。考虑下面的部分

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

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


} // end of randomFunction

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

答案 10 :(得分:0)

这里的许多答案建议使用同步。

但是,这取决于用例。

synced关键字自然具有内置的语言支持。这可能意味着JIT可以使用锁无法实现的方式优化同步块。例如它可以组合同步块。 使用同步块,在任何给定时间点仅允许一个线程访问一种方法。这是一个非常昂贵的操作。

锁通过允许出于不同目的配置各种锁来避免这种情况。一个锁下可以同步几个方法,而另一个锁下可以同步其他方法。这样可以提高并发性,并提高整体性能。

因此,对于一个较小的系统,该系统可以不进行并发操作并允许一个线程执行操作,则同步可以正常工作。否则,可以锁定键。