如果可以使用同步的(this),为什么还要使用ReentrantLock?


317

我试图了解是什么使并发锁如此重要,如果可以使用的话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;
}

1
顺便说一句,所有Java内部锁本质上都是可重入的。
Aniket Thakur

@pongapundit,因此synchronized(this){synchronized(this){//some code}}不会导致死锁。对于内在锁定,如果他们在资源上获得了监视,并且再次希望获得它,则可以在没有死锁的情况下获得它。
Aniket Thakur

Answers:


474

一个ReentrantLock的非结构化的,不像synchronized结构-即你不需要使用块结构锁,甚至可以举行跨越方法的锁。一个例子:

private ReentrantLock lock;

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

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

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


除此之外,还ReentrantLock支持锁定轮询支持超时的可中断锁定等待ReentrantLock它还支持可配置的公平性策略,从而允许更灵活的线程调度。

此类的构造接受一个可选的公平,该参数。true在竞争状态下设置set时,锁倾向于授予对等待时间最长的线程的访问。否则,此锁不能保证任何特定的访问顺序。使用许多线程访问的公平锁的程序可能会比使用默认设置的程序显示较低的总体吞吐量(即,速度较慢;通常要慢得多),但获得锁并保证没有饥饿的时间差异较小。但是请注意,锁的公平性不能保证线程调度的公平性。因此,使用公平锁的许多线程之一可能会连续多次获得它,而其他活动线程未进行且当前未持有该锁。另请注意,未定时tryLock方法不遵守公平性设置。如果锁定可用,即使其他线程正在等待,它将成功。


ReentrantLock 可能还具有更大的可扩展性,在更高的竞争下表现更好。您可以在此处了解更多信息。

但是,此主张遭到了质疑;看到以下评论:

在可重入锁测试中,每次都会创建一个新锁,因此没有互斥锁,并且结果数据无效。另外,IBM链接不提供基础基准测试的源代码,因此无法描述测试是否正确进行。


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

答案很简单-在您确实需要它提供的东西时使用它synchronized,例如定时锁等待,可中断锁等待,非块结构锁,多个条件变量或锁轮询。ReentrantLock它还具有可伸缩性的好处,如果您实际遇到竞争激烈的情况,则应使用它,但是请记住,绝大多数synchronized块几乎从未表现出任何竞争,更不用说竞争激烈了。我建议您进行同步开发,直到证明同步不足为止,而不是简单地假设“性能会更好”。ReentrantLock。请记住,这些是面向高级用户的高级工具。(并且真正的高级用户倾向于确信可以找到的最简单的工具,直到他们确信简单的工具是不合适的。)和往常一样,首先使其正确,然后再担心是否必须使其更快。


26
到lycog.com的“已知更具扩展性”链接应该删除。在可重入锁测试中,每次都会创建一个新锁,因此没有互斥锁,并且结果数据无效。另外,IBM链接不提供基础基准测试的源代码,因此无法描述测试是否正确执行。就我个人而言,我将删除关于可伸缩性的全部内容,因为基本上不支持整个声明。
2013年

2
我根据您的回复修改了该帖子。
oldrinb

6
如果您最关心性能,那么别忘了寻找根本不需要同步的方法。
mcoolive 2014年

2
表演对我完全没有意义。如果可重入锁的性能更好,那为什么不仅仅像可重入锁那样以相同的方式实现同​​步呢?
tObi

2
@ user2761895 ReentrantLockPseudoRandomLycog链接中的代码在每次调用时都使用全新的无竞争锁,setSeed并且next
oldrinb '17

14

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

您是对的,可以使用synchronized(this)代替,ReentrantReadWriteLock但并非总是如此。

如果您想更好地理解是什么使ReentrantReadWriteLock特殊之处,请查找有关生产者-消费者线程同步的一些信息。

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

顺便说一句,使用synchronized(this)-以及通常使用公共类实例进行锁定-可能会出现问题,因为它会使您的代码陷入潜在的死锁,因为其他人可能会试图在程序中的其他位置尝试锁定您的对象。


为防止潜在的死锁,因为在游戏中其他人可能不知情地试图锁定您的对象,请使用私有对象实例作为同步监视器,如下所示: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta

9

从有关ReentrantLock的 oracle文档页面:

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

  1. 一个ReentrantLock的由线程拥有最后成功锁定,但尚未解锁的。当该锁不属于另一个线程时,调用锁的线程将返回并成功获取该锁。如果当前线程已经拥有该锁,则该方法将立即返回。

  2. 此类的构造函数接受一个可选的fairness参数。设置为true时,在争用下, 锁倾向于授予对等待时间最长的线程的访问。否则,此锁不能保证任何特定的访问顺序。

根据本文介绍的ReentrantLock主要功能

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

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

看看Benjamen的这篇文章,了解不同类型的ReentrantLocks的用法


2

您可以将可重入锁与公平策略或超时一起使用,以避免线程匮乏。您可以应用线程公平性策略。这将有助于避免线程永远等待获取您的资源。

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

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

同样,如果Synchronize无法逃避阻止,它可以无限期阻止。Reentrantlock可以设置超时。


1

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

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


1

假设此代码在线程中运行:

private static ReentrantLock lock = new ReentrantLock();

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

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


0

要记住的一件事是:

名称“ ReentrantLock ”给出了关于其他锁定机制不是可重入的错误信息。这不是真的。通过“同步”获取的锁在Java中也可以重新输入。

关键区别在于“同步”使用内在锁(每个对象都有一个),而Lock API则没有。


0

我认为wait / notify / notifyAll方法不属于Object类,因为它使用很少使用的方法污染所有对象。在专用的Lock类上,它们更有意义。因此,从这个角度来看,最好使用专门针对当前工作设计的工具-ReentrantLock。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.