简体   繁体   English

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

[英]Why use a ReentrantLock if one can use synchronized(this)?

I'm trying to understand what makes the lock in concurrency so important if one can use synchronized (this) .如果可以使用synchronized (this)我试图了解是什么使并发锁定如此重要。 In the dummy code below, I can do either:在下面的虚拟代码中,我可以执行以下任一操作:

  1. synchronized the entire method or synchronize the vulnerable area ( synchronized(this){...} )同步整个方法或同步易受攻击的区域( synchronized(this){...}
  2. OR lock the vulnerable code area with a ReentrantLock.或者使用 ReentrantLock 锁定易受攻击的代码区域。

Code:代码:

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

A ReentrantLock is unstructured , unlike synchronized constructs -- ie you don't need to use a block structure for locking and can even hold a lock across methods. ReentrantLock非结构化的,不像synchronized结构——也就是说,你不需要使用块结构来锁定,甚至可以跨方法持有一个锁。 An example:一个例子:

private ReentrantLock lock;

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

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

Such flow is impossible to represent via a single monitor in a synchronized construct.这种流不可能通过synchronized结构中的单个监视器来表示。


Aside from that, ReentrantLock supports lock polling and interruptible lock waits that support time-out .除此之外, ReentrantLock支持锁轮询支持超时的可中断锁等待 ReentrantLock also has support for configurable fairness policy , allowing more flexible thread scheduling. ReentrantLock还支持可配置的公平策略,允许更灵活的线程调度。

The constructor for this class accepts an optional fairness parameter.此类的构造函数接受一个可选的公平参数。 When set true , under contention, locks favor granting access to the longest-waiting thread.当设置为true ,在争用true下,锁倾向于授予对等待时间最长的线程的访问权限。 Otherwise this lock does not guarantee any particular access order.否则这个锁不能保证任何特定的访问顺序。 Programs using fair locks accessed by many threads may display lower overall throughput (ie, are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation.与使用默认设置的程序相比,使用由多个线程访问的公平锁的程序可能会显示出较低的总体吞吐量(即更慢;通常慢得多),但在获取锁和保证不出现饥饿的时间上有较小的差异。 Note however, that fairness of locks does not guarantee fairness of thread scheduling.但是请注意,锁的公平性并不能保证线程调度的公平性。 Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock.因此,使用公平锁的许多线程之一可能会连续多次获得它,而其他活动线程没有进行并且当前没有持有该锁。 Also note that the untimed tryLock method does not honor the fairness setting.另请注意,未计时的tryLock方法不遵守公平设置。 It will succeed if the lock is available even if other threads are waiting.即使其他线程正在等待,如果锁可用,它也会成功。


ReentrantLock may also be more scalable , performing much better under higher contention. ReentrantLock可能更具可扩展性,更高的竞争下进行好得多。 You can read more about this here .您可以在此处阅读更多相关信息。

This claim has been contested, however;然而,这种说法受到了质疑。 see the following comment:请参阅以下评论:

In the reentrant lock test, a new lock is created each time, thus there is no exclusive locking and the resulting data is invalid.在可重入锁测试中,每次都会创建一个新锁,因此没有排他锁,结果数据无效。 Also, the IBM link offers no source code for the underlying benchmark so its impossible to characterize whether the test was even conducted correctly.此外,IBM 链接不提供基础基准测试的源代码,因此无法表征测试是否正确进行。


When should you use ReentrantLock s?什么时候应该使用ReentrantLock According to that developerWorks article...根据那篇 developerWorks 文章...

The answer is pretty simple -- use it when you actually need something it provides that synchronized doesn't, like timed lock waits, interruptible lock waits, non-block-structured locks, multiple condition variables, or lock polling.答案非常简单——当您确实需要它提供的synchronized功能不提供的东西时使用它,例如定时锁等待、可中断锁等待、非块结构锁、多个条件变量或锁轮询。 ReentrantLock also has scalability benefits, and you should use it if you actually have a situation that exhibits high contention, but remember that the vast majority of synchronized blocks hardly ever exhibit any contention, let alone high contention. ReentrantLock还具有可扩展性优势,如果您确实遇到了高争用情况,则应该使用它,但请记住,绝大多数synchronized块几乎不会出现任何争用,更不用说高争用了。 I would advise developing with synchronization until synchronization has proven to be inadequate, rather than simply assuming "the performance will be better" if you use ReentrantLock .我建议使用同步进行开发,直到证明同步不足为止,而不是简单地假设使用ReentrantLock “性能会更好”。 Remember, these are advanced tools for advanced users.请记住,这些是面向高级用户的高级工具。 (And truly advanced users tend to prefer the simplest tools they can find until they're convinced the simple tools are inadequate.) As always, make it right first, and then worry about whether or not you have to make it faster. (真正的高级用户往往更喜欢他们能找到的最简单的工具,直到他们确信简单的工具是不够的。)一如既往,先把它做好,然后再担心是否必须让它更快。


One final aspect that's gonna become more relevant in the near future has to do with Java 15 and Project Loom .在不久的将来会变得更加相关的最后一个方面与Java 15 和 Loom 项目有关 In the (new) world of virtual threads, the underlying scheduler would be able to work much better with ReentrantLock than it's able to do with synchronized , that's true at least in the initial Java 15 release but may be optimized later.在虚拟线程的(新)世界中,底层调度程序使用ReentrantLock比使用synchronized能够更好地工作,至少在最初的 Java 15 版本中是这样,但以后可能会进行优化。

In the current Loom implementation, a virtual thread can be pinned in two situations: when there is a native frame on the stack — when Java code calls into native code (JNI) that then calls back into Java — and when inside a synchronized block or method.在当前的 Loom 实现中,可以在两种情况下固定虚拟线程:当堆栈上有本机帧时——当 Java 代码调用本机代码(JNI)然后回调到 Java 时——以及当在synchronized块或方法。 In those cases, blocking the virtual thread will block the physical thread that carries it.在这些情况下,阻塞虚拟线程将阻塞承载它的物理线程。 Once the native call completes or the monitor released (the synchronized block/method is exited) the thread is unpinned.一旦本机调用完成或监视器释放( synchronized块/方法退出),线程将被取消固定。

If you have a common I/O operation guarded by a synchronized , replace the monitor with a ReentrantLock to let your application benefit fully from Loom's scalability boost even before we fix pinning by monitors (or, better yet, use the higher-performance StampedLock if you can).如果您有一个由synchronized保护的公共 I/O 操作,请用ReentrantLock替换监视器,即使在我们修复监视器固定之前,您的应用程序也可以充分受益于 Loom 的可扩展性提升(或者,更好的是,使用更高性能的StampedLock如果你可以)。

ReentrantReadWriteLock is a specialized lock whereas synchronized(this) is a general purpose lock. ReentrantReadWriteLock是一种专用锁,而synchronized(this)是一种通用锁。 They are similar but not quite the same.它们很相似,但并不完全相同。

You are right in that you could use synchronized(this) instead of ReentrantReadWriteLock but the opposite is not always true.你是对的,你可以使用synchronized(this)而不是ReentrantReadWriteLock但相反的情况并不总是如此。

If you'd like to better understand what makes ReentrantReadWriteLock special look up some information about producer-consumer thread synchronization.如果您想更好地了解ReentrantReadWriteLock特殊之处,请查找有关生产者-消费者线程同步的一些信息。

In general you can remember that whole-method synchronization and general purpose synchronization (using the synchronized keyword) can be used in most applications without thinking too much about the semantics of the synchronization but if you need to squeeze performance out of your code you may need to explore other more fine-grained, or special-purpose synchronization mechanisms.一般来说,您可以记住全方法同步和通用同步(使用synchronized关键字)可以在大多数应用程序中使用,而无需过多考虑同步的语义,但如果您需要从代码中挤出性能,您可能需要探索其他更细粒度或特殊用途的同步机制。

By the way, using synchronized(this) - and in general locking using a public class instance - can be problematic because it opens up your code to potential dead-locks because somebody else not knowingly might try to lock against your object somewhere else in the program.顺便说一句,使用synchronized(this) - 通常使用公共类实例进行锁定 - 可能会出现问题,因为它会将您的代码打开到潜在的死锁中,因为其他人可能会在不知情的情况下尝试锁定您的对象程序。

From oracle documentation page about ReentrantLock :来自关于ReentrantLock 的oracle 文档页面:

A reentrant mutual exclusion Lock with the same basic behaviour and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.可重入互斥锁与使用同步方法和语句访问的隐式监视器锁具有相同的基本行为和语义,但具有扩展功能。

  1. A ReentrantLock is owned by the thread last successfully locking, but not yet unlocking it. ReentrantLock由上次成功锁定但尚未解锁的线程拥有。 A thread invoking lock will return, successfully acquiring the lock, when the lock is not owned by another thread.当另一个线程不拥有锁时,调用锁的线程将返回并成功获取锁。 The method will return immediately if the current thread already owns the lock.如果当前线程已经拥有锁,该方法将立即返回。

  2. The constructor for this class accepts an optional fairness parameter.此类的构造函数接受一个可选的公平参数。 When set true, under contention, locks favor granting access to the longest-waiting thread .当设置为 true 时,在争用情况下,锁倾向于授予对等待时间最长的线程的访问权限 Otherwise this lock does not guarantee any particular access order.否则这个锁不能保证任何特定的访问顺序。

ReentrantLock key features as per this article根据本文的ReentrantLock关键功能

  1. Ability to lock interruptibly.能够中断锁定。
  2. Ability to timeout while waiting for lock.能够在等待锁定时超时。
  3. Power to create fair lock.创建公平锁定的权力。
  4. API to get list of waiting thread for lock.获取等待锁的线程列表的 API。
  5. Flexibility to try for lock without blocking.灵活地尝试锁定而不阻塞。

You can use ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock to further acquire control on granular locking on read and write operations.您可以使用ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock进一步获取对读写操作的粒度锁定的控制。

Have a look at this article by Benjamen on usage of different type of ReentrantLocks看看 Benjamen 关于不同类型ReentrantLocks 的使用的这篇文章

Synchronized locks does not offer any mechanism of waiting queue in which after the execution of one thread any thread running in parallel can acquire the lock.同步锁不提供任何等待队列的机制,在一个线程执行后,任何并行运行的线程都可以获取锁。 Due to which the thread which is there in the system and running for a longer period of time never gets chance to access the shared resource thus leading to starvation.因此,系统中存在并运行较长时间的线程永远不会有机会访问共享资源,从而导致饥饿。

Reentrant locks are very much flexible and has a fairness policy in which if a thread is waiting for a longer time and after the completion of the currently executing thread we can make sure that the longer waiting thread gets the chance of accessing the shared resource hereby decreasing the throughput of the system and making it more time consuming.可重入锁非常灵活并且具有公平策略,如果一个线程等待的时间较长,并且在当前执行的线程完成后,我们可以确保等待时间较长的线程获得访问共享资源的机会,从而减少系统的吞吐量并使其更耗时。

You can use reentrant locks with a fairness policy or timeout to avoid thread starvation.您可以使用具有公平策略或超时的可重入锁来避免线程饥饿。 You can apply a thread fairness policy.您可以应用线程公平策略。 it will help avoid a thread waiting forever to get to your resources.这将有助于避免线程永远等待获取您的资源。

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

The "fairness policy" picks the next runnable thread to execute. “公平策略”选择下一个可运行的线程来执行。 It is based on priority, time since last run, blah blah它基于优先级,自上次运行以来的时间,等等

also, Synchronize can block indefinitely if it cant escape the block.此外,如果无法逃脱阻止,Synchronize 可以无限期阻止。 Reentrantlock can have timeout set. Reentrantlock 可以设置超时。

One thing to keep in mind is :要记住的一件事是:

The name ' ReentrantLock ' gives out a wrong message about other locking mechanism that they are not re-entrant.名称“ ReentrantLock ”给出了关于它们不可重入的其他锁定机制的错误信息。 This is not true.这不是真的。 Lock acquired via 'synchronized' is also re-entrant in Java.通过“同步”获得的锁在 Java 中也是可重入的。

Key difference is that 'synchronized' uses intrinsic lock ( one that every Object has ) while Lock API doesn't.主要区别在于“同步”使用内在锁(每个对象都有),而 Lock API 没有。

Lets assume this code is running in a thread:让我们假设这段代码在一个线程中运行:

private static ReentrantLock lock = new ReentrantLock();

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

Because the thread owns the lock it will allow multiple calls to lock(), so it re-enter the lock.因为线程拥有锁,它将允许多次调用 lock(),所以它重新进入锁。 This can be achieved with a reference count so it doesn't has to acquire lock again.这可以通过引用计数来实现,因此它不必再次获取锁。

I think the wait/notify/notifyAll methods don't belong on the Object class as it pollutes all objects with methods that are rarely used.我认为 wait/notify/notifyAll 方法不属于 Object 类,因为它使用很少使用的方法污染所有对象。 They make much more sense on a dedicated Lock class.它们在专用的 Lock 类上更有意义。 So from this point of view, perhaps it's better to use a tool that is explicitly designed for the job at hand - ie ReentrantLock.所以从这个角度来看,也许最好使用为手头的工作明确设计的工具——即 ReentrantLock。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么 CopyOnWriteArrayList 使用 ReentrantLock 而使用 ReentrantReadWriteLock? - Why CopyOnWriteArrayList use ReentrantLock but ReentrantReadWriteLock? 我可以修改 ReentrantLock 以便可以将它与资源一起使用吗? - Can i modify ReentrantLock so one can use it with try with resources? 为什么 ArrayBlockingQueue 构造函数使用 ReentrantLock 来获得可见性? - Why ArrayBlockingQueue constructor use ReentrantLock for visibility? 为什么在演示中同步工作时ReentrantLock不起作用? - Why ReentrantLock is not working while synchronized work in the demo? 为什么竞争条件只能用 ReentrantLock 解决而不是同步 - Why race condition is only solved with ReentrantLock and not synchronized ReentrantLock用例 - ReentrantLock use case "对 ReentrantLock 实际使用 lockInterruptibly" - Actual use of lockInterruptibly for a ReentrantLock 从源代码看,ReentrantLock.tryLock()如何工作,它不使用同步块或函数 - How does ReentrantLock.tryLock() works as it seems from source code that it does not use synchronized block or function 在ConcurrentHashMap中,为什么不在段上使用ReentrantReadWriteLock代替ReentrantLock - In ConcurrentHashMap, why don't use ReentrantReadWriteLock in place of ReentrantLock on segment 为什么要使用同步方法制作动画? - Why Use Synchronized Methods for Animation?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM