浅谈Java中的 Synchronized 与 ReentrantLock

236 阅读2分钟

我正在参与掘金创作者训练营第6期,点击了解活动详情

认识 ReentrantLock(可重入的互斥锁)

JDK5开始,Java中引入一个工具包,专用于处理并发场景 -java.util.concurrent(常说的:JUC),它提供了大量更高级的并发功能,大大简化多线程程序的处理。 下面将介绍替代 synchronized 的 ReentrantLock(可重入互斥锁)。

传统 synchronized 加锁

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

public class Counter { // 定义加法器
    private int count;  
    // 成员方法
    public void add(int n) {
        synchronized(this) {  // 使用 synchronized 加锁处理
            count += n;
        }
    }
}

使用 ReentrantLock 替代

public class Counter {
    private final Lock lock = new ReentrantLock(); // 创建私有 ReentrantLock对象 仅限本类使用
    private int count;

    public void add(int n) {
        // 调lock()获取锁
        lock.lock();
        try {
            count += n;
        } finally {
            // 用完必须释放锁
            lock.unlock();
        }
    }
}

synchronized 与 ReentrantLock 异同❗

  • ReentrantLock 是可重入互斥锁,与 synchronized 一样,同一线程可多次获取同一个锁对象

  • 与 synchronized 不同的是,ReentrantLock 可尝试获取锁:tryLock(arg1, arg2) 方法

if (lock.tryLock(1, TimeUnit.SECONDS)) {  // 调用 tryLock() 尝试获取
    try {
        ...
    } finally {
        lock.unlock(); // 释放锁
    }
}

上述代码 调用tryLock()在尝试获取锁对象,最多等待1秒。 若1秒后仍未获取到锁,tryLock() 即返回false,程序即进行其他处理,并不是无限等待下去。

综上,可知 ReentrantLock 比直接使用 synchronized 更安全,就算线程在 tryLock()失败时,也担心死锁问题。

使用 Condition 对象来实现 wait()notify() 功能

class TaskQueue {
    private final Lock lock = new ReentrantLock();  // 定义 ReentrantLock对象
    private final Condition condition = lock.newCondition(); // 由 ReentrantLock对象获取到 Condition
    private Queue<String> queue = new LinkedList<>();  // 定义队列

    public void addTask(String s) {
        lock.lock();  // 获取锁对象
        try {
            queue.add(s);  //元素入队
            condition.signalAll();   // condition对象调用 signalAll() 类似于 notifyAll()
        } finally {
            lock.unlock();  // 释放锁对象
        }
    }

    public String getTask() {
        lock.lock();  // 获取锁对象
        try {
            while (queue.isEmpty()) {
                condition.await();  // 调用 await() 等待
            }
            return queue.remove();  
        } finally {
            lock.unlock(); // 释放锁对象
        }
    }
}

Condition 对象必须从 Lock 实例的 newCondition() 方法返回,才能获得绑定 Lock 实例的 Condition 实例。

Condition 提供的 await()、signal()、signalAll() 原理和 synchronized 锁对象的 wait()、notify()、notifyAll() 是一致的

  • await()会释放当前锁,进入等待状态;
  • signalAll() 会唤醒所有等待的线程;
  • signal() 会唤醒某个等待线程

与上面 tryLock()类似,condition.await() 可以在等待指定时间后,还没被其他线程通过signal()或signalAll()唤醒,即可自己醒来。

if (condition.await(1, TimeUnit.SECOND)) {
    // 被其他线程唤醒后 处理逻辑
} else {
    // 指定时间内没有被其他线程唤醒 处理逻辑
}

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

总结几点💫

  • ReentrantLock 可以替代 synchronized 进行同步处理机制
  • 使用 ReentrantLock 获取锁更安全
  • 先获取到锁,再执行try {...}代码块,最后在finally块释放锁对象
  • ReentrantLock 可使用tryLock()尝试获取锁
  • Condition 可替代已经过时的 wait()和notify()
  • Condition 对象必须从 Lock 对象获取

掘金(JUEJIN)一起进步,一起成长!