Guide to java.util.concurrent.Locks – java.util.concurrent.Locks指南

最后修改: 2017年 2月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Simply put, a lock is a more flexible and sophisticated thread synchronization mechanism than the standard synchronized block.

简单地说,锁是一种比标准同步块更灵活、更复杂的线程同步机制。

The Lock interface has been around since Java 1.5. It’s defined inside the java.util.concurrent.lock package, and it provides extensive operations for locking.

接口自Java 1.5以来就一直存在。它被定义在java.util.concurrent.lock包内,它为锁定提供了广泛的操作。

In this tutorial, we’ll explore different implementations of the Lock interface and their applications.

在本教程中,我们将探讨Lock接口的不同实现及其应用。

2. Differences Between Lock and Synchronized Block

2.锁和同步块的区别

There are a few differences between the use of synchronized block and using Lock APIs:

使用同步block和使用Lock API之间有一些区别。

  • A synchronizedblock is fully contained within a method. We can have Lock APIs lock() and unlock() operation in separate methods.
  • A synchronized block doesn’t support the fairness. Any thread can acquire the lock once released, and no preference can be specified. We can achieve fairness within the Lock APIs by specifying the fairness property. It makes sure that the longest waiting thread is given access to the lock.
  • A thread gets blocked if it can’t get an access to the synchronized block. The Lock API provides tryLock() method. The thread acquires lock only if it’s available and not held by any other thread. This reduces blocking time of thread waiting for the lock.
  • A thread that is in “waiting” state to acquire the access to synchronized block can’t be interrupted. The Lock API provides a method lockInterruptibly() that can be used to interrupt the thread when it’s waiting for the lock.

3. Lock API

3.锁定 API

Let’s take a look at the methods in the Lock interface:

让我们来看看Lock接口中的方法。

  • void lock() – Acquire the lock if it’s available. If the lock isn’t available, a thread gets blocked until the lock is released.
  • void lockInterruptibly() – This is similar to the lock(), but it allows the blocked thread to be interrupted and resume the execution through a thrown java.lang.InterruptedException.
  • boolean tryLock() – This is a nonblocking version of lock() method. It attempts to acquire the lock immediately, return true if locking succeeds.
  • boolean tryLock(long timeout, TimeUnit timeUnit) – This is similar to tryLock(), except it waits up the given timeout before giving up trying to acquire the Lock.
  • void unlock() unlocks the Lock instance.

A locked instance should always be unlocked to avoid deadlock condition.

锁定的实例应该总是被解锁,以避免死锁情况。

A recommended code block to use the lock should contain a try/catch and finally block:

建议使用锁的代码块应该包含一个try/catchfinally块。

Lock lock = ...; 
lock.lock();
try {
    // access to the shared resource
} finally {
    lock.unlock();
}

In addition to the Lock interface, we have a ReadWriteLock interface that maintains a pair of locks, one for read-only operations and one for the write operation. The read lock may be simultaneously held by multiple threads as long as there is no write.

除了Lock接口外,我们还有一个ReadWriteLock接口,它维护一对锁,一个用于只读操作,一个用于写操作。只要没有写操作,读锁可以由多个线程同时持有。

ReadWriteLock declares methods to acquire read or write locks:

ReadWriteLock声明了获取读或写锁的方法。

  • Lock readLock() returns the lock that’s used for reading.
  • Lock writeLock() returns the lock that’s used for writing.

4. Lock Implementations

4.锁的实施

4.1. ReentrantLock

4.1.可重入的锁

ReentrantLock class implements the Lock interface. It offers the same concurrency and memory semantics as the implicit monitor lock accessed using synchronized methods and statements, with extended capabilities.

ReentrantLock类实现了Lock接口。它提供了与使用同步方法和语句访问的隐式监控锁相同的并发性和内存语义,并具有扩展功能。

Let’s see how we can use ReentrantLock for synchronization:

让我们看看如何使用ReentrantLock进行同步。

public class SharedObject {
    //...
    ReentrantLock lock = new ReentrantLock();
    int counter = 0;

    public void perform() {
        lock.lock();
        try {
            // Critical section here
            count++;
        } finally {
            lock.unlock();
        }
    }
    //...
}

We need to make sure that we are wrapping the lock() and the unlock() calls in the try-finally block to avoid the deadlock situations.

我们需要确保将lock()unlock()调用包裹在try-finally块中,以避免出现死锁情况。

Let’s see how the tryLock() works:

让我们看看tryLock()是如何工作的。

public void performTryLock(){
    //...
    boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
    
    if(isLockAcquired) {
        try {
            //Critical section here
        } finally {
            lock.unlock();
        }
    }
    //...
}

In this case, the thread calling tryLock() will wait for one second and will give up waiting if the lock isn’t available.

在这种情况下,调用tryLock()的线程将等待一秒钟,如果锁不可用,将放弃等待。

4.2. ReentrantReadWriteLock

4.2.可重入的读写锁

ReentrantReadWriteLock class implements the ReadWriteLock interface.

ReentrantReadWriteLock类实现了ReadWriteLock接口。

Let’s see the rules for acquiring the ReadLock or WriteLock by a thread:

让我们看看一个线程获取ReadLockWriteLock的规则。

  • Read Lock – If no thread acquired the write lock or requested for it, multiple threads can acquire the read lock.
  • Write Lock – If no threads are reading or writing, only one thread can acquire the write lock.

Let’s look at how to make use of the ReadWriteLock:

我们来看看如何利用ReadWriteLock

public class SynchronizedHashMapWithReadWriteLock {

    Map<String,String> syncHashMap = new HashMap<>();
    ReadWriteLock lock = new ReentrantReadWriteLock();
    // ...
    Lock writeLock = lock.writeLock();

    public void put(String key, String value) {
        try {
            writeLock.lock();
            syncHashMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    ...
    public String remove(String key){
        try {
            writeLock.lock();
            return syncHashMap.remove(key);
        } finally {
            writeLock.unlock();
        }
    }
    //...
}

For both write methods, we need to surround the critical section with the write lock — only one thread can get access to it:

对于这两种写法,我们需要用写锁包围关键部分–只有一个线程可以获得对它的访问。

Lock readLock = lock.readLock();
//...
public String get(String key){
    try {
        readLock.lock();
        return syncHashMap.get(key);
    } finally {
        readLock.unlock();
    }
}

public boolean containsKey(String key) {
    try {
        readLock.lock();
        return syncHashMap.containsKey(key);
    } finally {
        readLock.unlock();
    }
}

For both read methods, we need to surround the critical section with the read lock. Multiple threads can get access to this section if no write operation is in progress.

对于这两种读取方法,我们需要用读取锁包围关键部分。如果没有写操作正在进行,多个线程可以获得对该部分的访问。

4.3. StampedLock

4.3.StampedLock

StampedLock is introduced in Java 8. It also supports both read and write locks.

StampedLock是在Java 8中引入的。它也支持读锁和写锁。

However, lock acquisition methods return a stamp that is used to release a lock or to check if the lock is still valid:

然而,锁获取方法会返回一个印章,用来释放锁或检查锁是否仍然有效。

public class StampedLockDemo {
    Map<String,String> map = new HashMap<>();
    private StampedLock lock = new StampedLock();

    public void put(String key, String value){
        long stamp = lock.writeLock();
        try {
            map.put(key, value);
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public String get(String key) throws InterruptedException {
        long stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

Another feature provided by StampedLock is optimistic locking. Most of the time, read operations don’t need to wait for write operation completion, and as a result of this, the full-fledged read lock isn’t required.

StampedLock提供的另一个功能是乐观的锁定。大多数时候,读操作不需要等待写操作完成,因此,不需要完整的读锁。

Instead, we can upgrade to read lock:

相反,我们可以升级为读锁。

public String readWithOptimisticLock(String key) {
    long stamp = lock.tryOptimisticRead();
    String value = map.get(key);

    if(!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlock(stamp);               
        }
    }
    return value;
}

5. Working With Conditions

5.使用条件s工作

The Condition class provides the ability for a thread to wait for some condition to occur while executing the critical section.

Condition类提供了线程在执行关键部分时等待某些条件发生的能力。

This can occur when a thread acquires the access to the critical section but doesn’t have the necessary condition to perform its operation. For example, a reader thread can get access to the lock of a shared queue that still doesn’t have any data to consume.

当一个线程获得了对关键部分的访问权,但不具备执行其操作的必要条件时,就会发生这种情况。例如,一个读者线程可以获得对一个共享队列的锁的访问权,但仍然没有任何数据需要消耗。

Traditionally Java provides wait(), notify() and notifyAll() methods for thread intercommunication.

传统上,Java提供了wait()notify()notifyAll()方法用于线程间的交流。

Conditions have similar mechanisms, but we can also specify multiple conditions:

条件s有类似的机制,但我们也可以指定多个条件。

public class ReentrantLockWithCondition {

    Stack<String> stack = new Stack<>();
    int CAPACITY = 5;

    ReentrantLock lock = new ReentrantLock();
    Condition stackEmptyCondition = lock.newCondition();
    Condition stackFullCondition = lock.newCondition();

    public void pushToStack(String item){
        try {
            lock.lock();
            while(stack.size() == CAPACITY) {
                stackFullCondition.await();
            }
            stack.push(item);
            stackEmptyCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public String popFromStack() {
        try {
            lock.lock();
            while(stack.size() == 0) {
                stackEmptyCondition.await();
            }
            return stack.pop();
        } finally {
            stackFullCondition.signalAll();
            lock.unlock();
        }
    }
}

6. Conclusion

6.结论

In this article, we saw different implementations of the Lock interface and the newly introduced StampedLock class.

在这篇文章中,我们看到了Lock接口的不同实现以及新引入的StampedLock类。

We also explored how we can make use of the Condition class to work with multiple conditions.

我们还探讨了如何利用Condition类来处理多个条件。

The complete code for this article is available over on GitHub.

本文的完整代码可在GitHub上获得