我试图理解什么使得锁在并发如此重要,如果一个人可以使用同步(这)。在下面的虚拟代码中,我可以这样做:

同步整个方法或同步脆弱区域(Synchronized (this){…}) 或者使用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;
}

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

你是对的,你可以使用synchronized(this)来代替ReentrantReadWriteLock,但相反的情况并不总是正确的。

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

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

顺便说一下,使用synchronized(this)——以及使用公共类实例的一般锁定——可能会有问题,因为它会导致代码出现潜在的死锁,因为其他人可能会在不知情的情况下尝试锁定程序中的其他地方的对象。


ReentrantLock是非结构化的,不像同步结构——也就是说,你不需要使用块结构来锁,甚至可以跨方法持有锁。一个例子:

private ReentrantLock lock;

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

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

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


除此之外,ReentrantLock还支持锁轮询和支持超时的可中断锁等待。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. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., 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. It will succeed if the lock is available even if other threads are waiting.


ReentrantLock也可能更具可伸缩性,在更高的争用下性能会更好。你可以在这里阅读更多相关内容。

然而,这一说法遭到了质疑;请看下面的评论:

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


什么时候应该使用ReentrantLocks?根据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. 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. 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. 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.


在不久的将来,与Java 15和Project Loom相关的最后一个方面将变得更加重要。在虚拟线程的(新)世界中,底层调度器使用ReentrantLock比使用synchronized工作得更好,至少在最初的Java 15版本中是这样,但以后可能会进行优化。

在当前的Loom实现中,虚拟线程可以在两种情况下固定:当堆栈上有本机帧时——当Java代码调用本机代码(JNI),然后JNI再调用Java时——以及在同步块或方法中时。在这些情况下,阻塞虚拟线程将阻塞承载它的物理线程。一旦本机调用完成或监视器被释放(同步块/方法被退出),线程就被解除固定。

如果你有一个常用的I/O操作由synchronized保护,用ReentrantLock替换监视器,让你的应用程序在我们修复监视器固定之前充分受益于Loom的可伸缩性提升(或者,如果可以的话,更好的是使用性能更高的StampedLock)。


从oracle文档页关于ReentrantLock:

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

A ReentrantLock is owned by the thread last successfully locking, but not yet unlocking it. 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. The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order.

ReentrantLock键的特性如本文所述

能够可中断地锁定。 能力超时,而等待锁定。 创造公平锁的权力。 API来获取等待锁的线程列表。 灵活地尝试锁定而不阻塞。

你可以使用ReentrantReadWriteLock。ReadLock ReentrantReadWriteLock。WriteLock以进一步获得对读写操作的粒度锁的控制。

看看Benjamen关于不同类型ReentrantLocks使用的文章吧


您可以使用带有公平策略或超时的重入锁来避免线程饥饿。您可以应用线程公平策略。这将有助于避免线程永远等待获取资源。

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

“公平策略”选择下一个要执行的可运行线程。它是基于优先级,从上次运行到现在的时间等等

同时, 同步如果不能脱离阻塞,可以无限阻塞。Reentrantlock可以设置超时。


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

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


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

private static ReentrantLock lock = new ReentrantLock();

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

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


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

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


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