ReentrantLock详解

本文详细解析了ReentrantLock的使用格式、底层实现、获取与释放锁的过程,对比了公平锁与非公平锁、可重入锁与不可重入锁的区别,介绍了ReentrantLock的可定时性特点及其实现单例模式的应用,最后比较了ReentrantLock与synchronized的异同。


ReentrantLock

  • 提供了无条件的,可轮询的,定时的以及可中断的锁获取操作
  • 加锁和解锁都是显式的

ReentrantLock使用格式

  Lock lock = new ReentrantLock();
  try{
      lock.lock();//加锁操作
  }finally{
      lock.unlock();
  }

使用过后必须释放锁

底层实现

当我们new一个ReentrantLock对象时,底层会帮我们new一个NonfairSync对象,NonfairSync FairSync都是基于AQS队列实现,AbstractQueuedSynchronizer简称为AQS队列。
它是基于先进先出FIFO实现的等待队列,AQS队列是由Node节点组成的双向连标实现啊的,所有的操作都是在这个AQS队列当中,如果一个线程获取锁就直接成功,如果失败了就将其放入等待队列当中。

获取锁

1)CAS操作抢占锁,抢占成功则修改锁的状态为1,将线程信息记录到锁当中,返回state=1
2)抢占不成功,tryAcquire获取锁资源,获取成功直接返回,获取不成功,新建一个检点插入到
当前AQS队列的尾部,acquireQueued(node)表示唤醒AQS队列中的节点再次去获取锁

释放锁

1)获取锁的状态值,释放锁将状态值-1
2)判断当前释放锁的线程和锁中保存的线程信息是否一致,不一致会抛出异常
3)状态只-1直到为0,锁状态值为0表示不再占用,为空闲状态

公平锁与非公平锁

  • 公平锁的实现就是谁等待时间最长,谁就先获取锁
  • 非公平锁就是随机获取的过程,谁运气好,cpu时间片轮询到哪个线程,哪个线程就能获取锁

公平锁

class Singleton1{
    private static Lock lock = new ReentrantLock(true);
    public static void test(){
        for(int i=0; i<2; i++){
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"获取了锁");
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
     public static void main(String[] args) {
             new Thread("线程A"){
            @Override
            public void run() {
                test();
            }
        }.start();
        new Thread("线程B"){
            @Override
            public void run() {
                test();
            }
        }.start();
        }
        }

运行结果为:
线程A获得了锁
线程B获得了锁
线程A获得了锁
线程B获得了锁

private static Lock lock = new ReentrantLock(true);

给定参数为(true)则为公平锁,哪个线程等待的时间越长,哪个线程获得锁

非公平锁

class Singleton1{
    private static Lock lock = new ReentrantLock();
    public static void test(){
        for(int i=0; i<2; i++){
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"获取了锁");
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
     public static void main(String[] args) {
             new Thread("线程A"){
            @Override
            public void run() {
                test();
            }
        }.start();
        new Thread("线程B"){
            @Override
            public void run() {
                test();
            }
        }.start();
        }
        }
private static Lock lock = new ReentrantLock();

参数为默认参数,为非公平锁,线程随机获得锁

可重入锁与不可重入锁

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

synchronized

public class TestDemo10 {
    public static synchronized void lock1(){
        System.out.println("lock1");
        lock2();
    }
    public static synchronized void lock2(){
        System.out.println("lock2");
    }
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                TestDemo10 lock = new TestDemo10();
                lock.lock1();
            }
        }.start();
        }
        }

运行结果:
在这里插入图片描述
所以synchronized为可重入锁

ReentrantLock

    private static Lock lock = new ReentrantLock();
    private static int i = 0;
        try{
            lock.lock();
            lock.lock();
            i++;
        } finally {
            lock.unlock();
            lock.unlock();
        }

运行不会被阻塞,所以R~~eentrantLock为可重入锁

可定时性

public class TestDemo10 {
   private  static ReentrantLock lock = new ReentrantLock();
       public static void main(String[] args) {
        Thread thread1 = new Thread("thread1"){
            @Override
            public void run() {
                try{
                    if(lock.tryLock(5, TimeUnit.SECONDS)){
                        TimeUnit.SECONDS.sleep(6);
                    }else{
                        System.out.println(Thread.currentThread().getName()+"获取锁失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread thread2 = new Thread("thread2"){
            @Override
            public void run() {
                try{
                    if(lock.tryLock(5, TimeUnit.SECONDS)){
                        TimeUnit.SECONDS.sleep(6);
                    }else{
                  System.out.println(Thread.currentThread().getName()+"获取锁失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if(lock.isHeldByCurrentThread()){
                        lock.unlock();
                    }
                }
            }
        };
        thread1.start();
        thread2.start();
        }
        }

运行结果为:
线程2获取锁失败

因为线程1先获得锁,执行睡眠6秒的操作,睡眠中不会释放掉锁,因此线程2在5秒内没有获取到锁。

ReentrantLock实现单例

   class Singleton1{
        private Singleton1(){
        }
    private static volatile Singleton1 instance;
    private static Lock lock = new ReentrantLock();
    //返回当前唯一一个实例
    static Singleton1 getInstance(){
        if(instance == null){//判断是否初始化
            try{
                lock.lock();
                if(instance == null){
                    instance = new Singleton1();
                //    System.out.println("single has been initialized by "+Thread.currentThread().getName());
                }
            } finally {
                lock.unlock();
            }
        }
        return instance;
    }
}

public class TestDemo11{
    public static void main(String[] args) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(Singleton1.getInstance());
                }
            }.start();
    }
}

ReentrantLock与synchronized的比较

相似点

它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,等到释放掉锁或者唤醒后才能继续获得锁。

区别

对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成

便利性:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

锁的细粒度和灵活度:ReenTrantLock优于Synchronized

### ReentrantLock 的详细用法与原理 #### 一、ReentrantLock 基本概念 `ReentrantLock` 是 `java.util.concurrent.locks` 包中的一个重要工具,用于提供比内置关键字 `synchronized` 更加灵活的锁控制功能。它是一个可重入互斥锁,支持公平和非公平两种模式,并且能够通过显式的 `lock()` 和 `unlock()` 方法来管理线程间的访问[^1]。 #### 二、主要特性 1. **显式加锁与解锁** 使用 `ReentrantLock` 需要手动调用 `lock()` 加锁以及在异常处理块中确保调用 `unlock()` 来释放锁。这种设计使得开发者可以更加精确地控制锁的行为。 2. **公平锁与非公平锁** 可以通过构造函数参数指定锁是否为公平锁。如果设置为公平锁,则会按照请求顺序分配锁;而默认情况下是非公平锁,允许抢占行为发生[^4]。 3. **条件变量支持** 利用 `Condition` 接口,`ReentrantLock` 提供了更细粒度的线程等待/通知机制。这相比于传统的 `Object.wait()` 和 `Object.notify()` 能够更好地满足复杂的业务需求[^2]。 4. **超时机制** 支持带有时间限制的锁定尝试 (`tryLock(long timeout, TimeUnit unit)`) ,从而避免死锁风险并提高程序健壮性。 5. **中断响应** 当前线程可以在等待获取锁的过程中被其他线程打断(`lockInterruptibly()`) 。这一特性对于构建高可用性和容错性强的应用非常重要[^2]。 #### 三、代码示例 下面展示了一个简单的例子说明如何使用 `ReentrantLock` 实现计数器增减操作: ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public void decrement() { lock.lock(); try { count--; } finally { lock.unlock(); } } public int getCount() { return count; } } ``` 此段代码定义了一个基于 `ReentrantLock` 的原子级整型计数器类,在每次修改共享资源之前都会先获得锁保护数据一致性[^2]。 另外还给出了测试不同类型的锁的方法演示: ```java private static void testLock(Lock lock, String lockType){ for(int i=0;i<10;i++){ // 尝试获取锁 lock.lock(); try{ System.out.println(Thread.currentThread().getName()+" acquired "+lockType+", iteration: "+i); // 模拟耗时操作 Thread.sleep(50); }catch(InterruptedException e){ e.printStackTrace(); }finally{ // 释放锁 lock.unlock(); } } } ``` 这段代码展示了如何创建多个线程分别去竞争同一个锁实例的情况下的表现差异[^3]。 #### 四、工作原理解析 - AQS(AbstractQueuedSynchronizer): `ReentrantLock` 底层依赖于AQS框架完成其核心逻辑实现。当某个线程试图占有该锁却失败时会被加入到一个FIFO队列当中排队等候直到前驱节点释放掉相应许可为止^。 - 公平 vs 非公平 : 如果选择了公平模式那么新到来的竞争者将严格按照先进先出来决定谁能成功拿到下一个机会;反之如果是非公平的话则存在一定的随机性可能让后来居上的情况出现即所谓的插队现象. ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值