ReentrantLock可重入锁

8/31/2021 多线程

# ReentrantLock可重入锁简单介绍✨

从JDK5开始,引入了一个高级的处理并发的java.util.concurrent包(就是常说的:JUC🎋),它提供了大量更高级的并发功能,能大大简化多线程程序的编写。这里将介绍JUC提供的,替代synchronized锁的ReentrantLock(可重入锁)

synchronized关键字用于加锁,但这种锁一是很重,二是获取时必须一直等待,没有额外的尝试机制。

# 传统的synchronized锁🎐

public class Counter {
    private int count;

    public void add(int n) {
        synchronized(this) {
            count += n;
        }
    }
}
1
2
3
4
5
6
7
8
9

# ReentrantLock替代🎋

public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count;

    public void add(int n) {
        // 获取锁
        lock.lock();
        try {
            count += n;
        } finally {
            // 最后必须释放锁
            lock.unlock();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 区别❗

ReentrantLock是可重入锁,它和synchronized一样,一个线程可以多次获取同一个锁。

和synchronized不同的是,ReentrantLock可以尝试获取锁

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}
1
2
3
4
5
6
7

上述代码在尝试获取锁的时候,最多等待1秒。如果1秒后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去。

所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁。

# 小结⭕

ReentrantLock可以替代synchronized进行同步;

ReentrantLock获取锁更安全;

必须先获取到锁,再进入try {...}代码块,最后使用finally保证释放锁;

可以使用tryLock()尝试获取锁。

# 使用Condition对象来实现wait和notify的功能。💥

class TaskQueue {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();

    public void addTask(String s) {
        lock.lock();
        try {
            queue.add(s);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public String getTask() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                condition.await();
            }
            return queue.remove();
        } finally {
            lock.unlock();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回,这样才能获得一个绑定了Lock实例的Condition实例。

Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的:

  • await()会释放当前锁,进入等待状态;

  • signal()会唤醒某个等待线程;

  • signalAll()会唤醒所有等待线程;

  • 唤醒线程从await()返回后需要重新获得锁。

此外,和tryLock()类似,await()可以在等待指定时间后,如果还没有被其他线程通过signal()或signalAll()唤醒,可以自己醒来

if (condition.await(1, TimeUnit.SECOND)) {
    // 被其他线程唤醒
} else {
    // 指定时间内没有被其他线程唤醒
}
1
2
3
4
5

可见,使用Condition配合Lock,我们可以实现更灵活的线程同步。

# 小结💫

Condition可以替代wait和notify;

Condition对象必须从Lock对象获取。