如果可以使用synchronized(this),为什么要使用ReentrantLock?

问题描述 投票:290回答:7

我试图理解是什么让并发锁如此重要如果可以使用synchronized (this)。在下面的虚拟代码中,我可以做到:

  1. 同步整个方法或同步易受攻击的区域(synchronized(this){...}
  2. 或者使用ReentrantLock锁定易受攻击的代码区域。

码:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}
java multithreading concurrency synchronize reentrantlock
7个回答
441
投票

ReentrantLock结构不同,synchronized是非结构化的 - 即你不需要使用块结构进行锁定,甚至可以跨方法保持锁定。一个例子:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

这种流动不可能通过synchronized构造中的单个监视器来表示。


除此之外,ReentrantLock支持lock pollinginterruptible lock waits that support time-outReentrantLock也支持configurable fairness policy,允许更灵活的线程调度。

此类的构造函数接受可选的fairness参数。设置true时,在争用下,锁定有利于授予对等待时间最长的线程的访问权限。否则,此锁定不保证任何特定的访问顺序。使用由许多线程访问的公平锁的程序可以显示比使用默认设置的程序更低的总吞吐量(即,更慢;通常慢得多),但是在获得锁并保证缺乏饥饿的时间方面具有较小的差异。但请注意,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁定的许多线程中的一个可以连续多次获得它,而其他活动线程没有进展并且当前没有保持锁定。另请注意,不定时的tryLock方法不符合公平性设置。如果锁即使可用,即使其他线程正在等待,它也会成功。


ReentrantLock也可能是more scalable,在更高的争用下表现更好。你可以阅读更多关于这个here的信息。

然而,这一主张受到质疑;看到以下评论:

在重入锁定测试中,每次都会创建一个新锁,因此没有排他锁定,结果数据无效。此外,IBM链接不提供底层基准测试的源代码,因此无法确定测试是否正确执行。


你什么时候应该使用ReentrantLocks?根据那篇developerWorks文章......

答案非常简单 - 在实际需要synchronized不提供的东西时使用它,如定时锁等待,可中断锁等待,非块结构锁,多个条件变量或锁定轮询。 ReentrantLock也具有可伸缩性的好处,如果你实际上有一个表现出高争用的情况你应该使用它,但要记住绝大多数synchronized块几乎没有表现出任何争用,更不用说高争用了。我建议用同步进行开发,直到同步被证明是不合适的,而不是简单地假设“性能会更好”,如果你使用ReentrantLock。请记住,这些是高级用户的高级工具。 (真正的高级用户倾向于选择他们能够找到的最简单的工具,直到他们确信简单的工具不合适。)一如既往,先做好,然后再担心你是否必须加快速度。


13
投票

ReentrantReadWriteLock是一个专门的锁,而synchronized(this)是一个通用锁。它们相似但不完全相同。

你是对的,你可以使用synchronized(this)而不是ReentrantReadWriteLock,但相反的情况并非总是如此。

如果你想更好地理解是什么让ReentrantReadWriteLock特别查找有关生产者 - 消费者线程同步的一些信息。

通常,您可以记住,整个方法同步和通用同步(使用synchronized关键字)可以在大多数应用程序中使用,而不必过多考虑同步的语义,但如果您需要从代码中挤出性能,则可能需要探索其他更细粒度或专用的同步机制。

顺便说一句,使用synchronized(this) - 并且通常使用公共类实例进行锁定 - 可能会有问题,因为它会将代码打开到潜在的死锁,因为其他人不会故意在程序中的其他位置尝试锁定对象。


8
投票

从oracle文档页面关于ReentrantLock

可重入互斥锁具有与使用同步方法和语句访问的隐式监视器锁相同的基本行为和语义,但具有扩展功能。

  1. ReentrantLock由最后成功锁定的线程拥有,但尚未解锁。当锁不是由另一个线程拥有时,线程调用锁将返回,成功获取锁。如果当前线程已拥有锁,则该方法将立即返回。
  2. 此类的构造函数接受可选的fairness参数。当设置为true时,在争用下,锁定有利于授予对等待时间最长的线程的访问权限。否则,此锁定不保证任何特定的访问顺序。

ReentrantLock根据此article的主要功能

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

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

看看Benjamen关于使用不同类型的ReentrantLocks的article


0
投票

您可以使用具有公平策略或超时的重入锁来避免线程不足。您可以应用线程公平策略。它将有助于避免线程永远等待您的资源。

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

“公平政策”选择下一个可运行的线程来执行。它基于优先级,自上次运行以来的时间,等等等等

另外,如果它无法逃脱阻止,则Synchronize可以无限期地阻塞。 Reentrantlock可以设置超时。


0
投票

同步锁不提供任何等待队列机制,其中在执行一个线程之后,并行运行的任何线程都可以获取锁。由此,系统中存在且运行较长时间的线程永远不会有机会访问共享资源,从而导致饥饿。

可重入锁非常灵活,并且具有公平策略,其中如果线程等待较长时间并且在当前正在执行的线程完成之后,我们可以确保较长等待线程获得访问共享资源的机会,从而减少系统的吞吐量并使其更耗时。


0
投票

让我们假设此代码在一个线程中运行:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

因为线程拥有锁,所以它将允许多次调用lock(),因此它重新进入锁。这可以通过引用计数来实现,因此不必再次获取锁定。


0
投票

要记住的一件事是: 名称“ReentrantLock”给出了关于其他锁定机制的错误消息,它们不是可重入的。这不是真的。通过'synchronized'获得的锁在Java中也是可重入的。

关键的区别在于'synchronized'使用内部锁(每个Object都有),而Lock API则没有。

© www.soinside.com 2019 - 2024. All rights reserved.