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

时间:2012-08-06 02:08:40

标签: java multithreading concurrency synchronize reentrantlock

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

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

        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;
    }
    

8 个答案:

答案 0 :(得分:433)

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链接不提供底层基准测试的源代码,因此无法确定测试是否正确执行。


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

  

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

答案 1 :(得分:13)

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

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

如果您想更好地了解使ReentrantReadWriteLock特殊的内容,请查看有关生产者 - 消费者线程同步的一些信息。

一般来说,你可以记住,整个方法同步和通用同步(使用synchronized关键字)可以在大多数应用程序中使用,而不必考虑太多关于同步的语义但如果你需要从代码中挤出性能,你可能需要探索其他更精细或特殊用途的同步机制。

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

答案 2 :(得分:7)

从oracle文档页面了解ReentrantLock

  

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

  1. ReentrantLock 归上次成功锁定的线程所有,但尚未解锁。当锁不是由另一个线程拥有时,线程调用锁将返回,成功获取锁。如果当前线程已拥有锁,则该方法将立即返回。

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

  3. 根据此article

    ReentrantLock 主要功能

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

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

      article

答案 3 :(得分:0)

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

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

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

也 如果它无法逃脱阻止,则同步可以无限期地阻塞。 Reentrantlock可以设置超时。

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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

private static ReentrantLock lock = new ReentrantLock();

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

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

答案 6 :(得分:0)

要记住的一件事是:

名称“ ReentrantLock ”给出了关于其他锁定机制不是可重入的错误消息。 这是不正确的。通过“同步”获得的锁在Java中也是可重入的。

主要区别在于,“同步”使用固有锁定(每个Object都具有),而Lock API没有。

答案 7 :(得分:0)

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