前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程之ReentrantLock类使用介绍

并发编程之ReentrantLock类使用介绍

作者头像
Java极客技术
发布2023-10-23 16:49:09
1910
发布2023-10-23 16:49:09
举报
文章被收录于专栏:Java极客技术

一、简介

在之前的线程系列文章中,我们介绍到了使用synchronized关键字可以实现线程同步安全的效果,以及采用wait()notify()notifyAll()方法,可以实现多个线程之间的通信协调,基本可以满足并发编程的需求。

但是采用synchronized进行加锁,这种锁一般都比较重,里面的实现机制也非常复杂,同时获取锁时必须一直等待,没有额外的尝试机制,如果编程不当,可能就容易发生死锁现象。

从 JDK 1.5 开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大的简化多线程程序的编写。

比如我们今天要介绍的java.util.concurrent.locks包提供的ReentrantLock类,一个可重入的互斥锁,它具有与使用synchronized加锁一样的特性,并且功能更加强大。

下面我们一起来学习一下ReentrantLock类的基本使用。

二、ReentrantLock 基本用法

在介绍ReentrantLock之前,我们先来看一下传统的使用synchronized对方法进行加锁的示例。

代码语言:javascript
复制
public class Counter {

    private int count;

    public void add() {
        synchronized(this) {
            count ++;
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
        }
    }

    public int getCount() {
        return count;
    }
}
代码语言:javascript
复制
public static void main(String[] args) throws InterruptedException {
    Counter counter = new Counter();

    // 创建5个线程,同时对count进行加一操作
    for (int i = 0; i < 5; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.add();
            }
        }).start();
    }

    // 假设休眠1秒,5个线程执行完毕
    Thread.sleep(1000);
    System.out.println("count:" + counter.getCount());
}

输出结果如下:

代码语言:javascript
复制
ThreadName:Thread-0, count:1
ThreadName:Thread-1, count:2
ThreadName:Thread-2, count:3
ThreadName:Thread-3, count:4
ThreadName:Thread-4, count:5
count:5

如果用ReentrantLock替代,只需要将Counter中的代码改造为如下:

代码语言:javascript
复制
public class Counter {

    private final Lock lock = new ReentrantLock();

    private int count;

    public void add() {
        // 加锁
        lock.lock();
        try {
            count ++;
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    
    public int getCount() {
        return count;
    }
}

运行程序,结果与上面一致,可以证明:ReentrantLock具备与synchronized一样的加锁功能。

同时,ReentrantLock还具备在指定的时间内尝试获取锁的机制,比如下面这行代码:

代码语言:javascript
复制
if (lock.tryLock(3, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}

尝试在 3 秒内获取锁,如果获取不到就返回false,程序不需要无限等待下去,这个功能在实际开发中使用非常的广泛。

从上面的示例代码,我们可以总结出synchronizedReentrantLock有以下几点不一样。

  • ReentrantLock需要手动调用加锁方法;而synchronized不需要,它采用了隐藏的加锁方式,借助 jvm 来实现
  • synchronized不需要考虑异常;而ReentrantLock获取锁之后,要在finally中正确的释放锁,否则会影响其它线程
  • ReentrantLock拥有尝试获取锁的超时机制,利用它可以避免无限等待;而synchronized不具备
  • synchronized是 Java 语言层面提供的语法;而ReentrantLock是 Java 代码实现的可重入锁

因此,在并发编程中,使用ReentrantLock比直接使用synchronized更灵活、更安全,采用tryLock(long time, TimeUnit unit)方法,即使未获取到锁也不会导致死锁。

三、ReentrantLock 和 synchronized 持有的对象监视器是同一个吗?

可能有的同学会发出这样的一个问题,使用ReentrantLock进行加锁和使用synchronized加锁,两者持有的对象监视器是同一个吗?

下面我们一起来看一个例子。

代码语言:javascript
复制
public class Counter {

    private final Lock lock = new ReentrantLock();

    private int count;


    public synchronized void methodA() {
        System.out.println("ThreadName:" + Thread.currentThread().getName() + ",begin methodA, count:" + getCount());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count ++;
        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());

    }

    public void methodB() {
        // 加锁
        lock.lock();
        try {
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ",begin methodB, count:" + getCount());
            Thread.sleep(3000);
            count ++;
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
        } catch (Exception e){
          e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}
代码语言:javascript
复制
public class MyThreadA extends Thread {

    private Counter counter;

    public MyThreadA(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        counter.methodA();
    }
}
代码语言:javascript
复制
public class MyThreadB extends Thread {

    private Counter counter;

    public MyThreadB(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        counter.methodB();
    }
}
代码语言:javascript
复制
public class MyThreadTest {

    public static void main(String[] args) {
        Counter counter = new Counter();

        MyThreadA threadA = new MyThreadA(counter);
        threadA.start();

        MyThreadB threadB = new MyThreadB(counter);
        threadB.start();
    }
}

看一下运行结果:

代码语言:javascript
复制
ThreadName:Thread-0,begin methodA, count:0
ThreadName:Thread-1,begin methodB, count:0
ThreadName:Thread-0, count:2
ThreadName:Thread-1, count:2

从日志上可以看出,采用两个线程分别采用synchronizedReentrantLock两种加锁方式对count进行操作,两个线程交替执行,可以得出一个结论:synchronizedReentrantLock持有的对象监视器不同。

四、Condition 基本用法

在之前的文章中,我们介绍了在synchronized同步方法/代码块中,使用wait()notify()notifyAll()可以实现线程之间的等待/通知模型。

ReentrantLock同样也可以,只需要借助Condition类即可实现,Condition提供的await()signal()signalAll()原理和synchronized锁对象的wait()notify()notifyAll()是一致的,并且其行为也是一样的。

我们还是先来看一个简单的示例。

代码语言:javascript
复制
public class Counter {

    private final Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    private int count;

    public void await(){
        // 加锁
        lock.lock();
        try {
            condition.await();
            System.out.println("await等待结束,count:" + getCount());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }


    public void signal(){
        // 加锁
        lock.lock();
        try {
            count++;
            // 唤醒某个等待线程
            condition.signal();
            // 唤醒所有等待线程
//            condition.signalAll();
            System.out.println("signal 唤醒通知完毕");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

代码语言:javascript
复制
public class MyThreadA extends Thread {

    private Counter counter;

    public MyThreadA(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        counter.await();
    }
}
代码语言:javascript
复制
public class MyThreadB extends Thread {

    private Counter counter;

    public MyThreadB(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        counter.signal();
    }
}
代码语言:javascript
复制
public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // 先启动执行等待的线程
        MyThreadA threadA = new MyThreadA(counter);
        threadA.start();

        Thread.sleep(3000);

        // 过3秒,再启动执行通知的线程
        MyThreadB threadB = new MyThreadB(counter);
        threadB.start();
    }
}

看一下运行结果:

代码语言:javascript
复制
signal 通知完毕
await等待结束,count:1

从结果上看很明显的看出,等待线程MyThreadA先启动,过了 3 秒之后再启动了MyThreadB,但是signal()方法先执行完毕,再通知await()方法执行,符合代码预期。

这个例子也证明了一点:condition.await()方法是释放了锁,不然signal()方法体不会被执行。

相比wait/notify/notifyAll的等待/通知模型,Condition更加灵活,理由有以下几点:

  • notify()方法唤醒线程时,被通知的线程由 Java 虚拟机随机选择;而采用ReentrantLock结合Condition可以实现有选择性地通知,这一特性在实际编程中非常实用
  • 一个Lock里面可以创建多个Condition实例,实现多路通知,使用多个Condition的应用场景很常见,比如ArrayBlockingQueue

五、小结

本文主要围绕ReentrantLock的基本使用做了一次简单的知识总结,如果有不正之处,请多多谅解,并欢迎批评指出。

六、参考

1、博客园 -五月的仓颉 - ReentrantLock的使用和Condition

2、 廖雪峰 - 使用ReentrantLock

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java极客技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、简介
  • 二、ReentrantLock 基本用法
  • 三、ReentrantLock 和 synchronized 持有的对象监视器是同一个吗?
  • 四、Condition 基本用法
  • 五、小结
  • 六、参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档