ReentrantLock

本文详细介绍了Java的可重入锁ReentrantLock,对比了它与synchronized的区别,提供了多种示例代码,包括同步执行、尝试执行、可中断执行等场景。文章还深入解析了ReentrantLock的源码,探讨了AQS和CAS的概念及其在锁实现中的作用,以及ABA问题。通过对ReentrantLock的学习,有助于提升编程能力和理解并发控制。
摘要由CSDN通过智能技术生成

目录

一、介绍

二、对比synchronized

三、示例代码

1 同步执行

2 尝试执行

3 尝试等待执行

4 可中断执行

5 可重入

四、源码解读

4.1 ReentrantLock及Lock

4.1.1 ReentrantLock类关系

4.1.2 ReentrantLock父类Lock以及方法

4.2 AQS

4.3 CAS

4.3.1 概念及特性

4.3.2 原子包 java.util.concurrent.atomic(锁自旋)

4.3.3 ABA 问题

五 总结


一、介绍

ReentrantLock是从jdk1.5定义的可重入的独享锁,其拥有比较灵活的使用方式,提供了公平锁和非公平锁功能,内部是基于乐观锁思想实现的争抢策略;主要依托AQS提供的模板方法,实现其具有自身特色的锁功能;深入学习ReentrantLock内部实现,有助于提升编码能力,提升编程思想,可谓说是益处多多。

二、对比synchronized

功能描述 ReentrantLock Synchronized
可重入锁 支持 支持
公平锁 支持 不支持
非公平锁 支持 支持
获取、释放锁顺序 无序 有序
获取锁方式

获取锁

尝试获取非阻塞

获取可被中断的锁

尝试可超时的锁的

获取锁

同一个线程,获取、释放锁顺序不同:

  • synchronized获取、释放锁是有顺序的,获取节点A的锁,然后获取节点B的锁,先释放节点B的锁,再释放节点A的锁。
  • ReentrantLock获取、释放锁是无序的,获取节点A的锁,然后获取节点B的锁,可不分先后分别释放节点A、B的锁。

获取锁方式:

ReentrantLock相比于synchronized增加了获取锁的方式,优化线程资源占用,通俗讲,在实际引用场景中,被锁的程序段需要一定的执行时间,而每个线程都需要排队执行此段代码,造成线程占用。举个极端例子,“A段代码”的执行时间为1s,在1s内有500次请求,即开启了500个线程,比如我们的容器Tomcat最大只开启了500个线程,那么“其他方法”将没有线程可执行,需要等待“A段代码”的线程释放,此场景我们需要平衡“A段代码”的线程使用,因此增加了其他获取锁的方式:

lockInterruptibly():获取锁,除非当前线程被中断。

tryLock():当此段代码未被锁时,才会获取锁。不会等待获取锁,不会有队列保持公平,如果一定要划分公平与非公平,那么属于非公平锁。

tryLock(long time, TimeUnit unit):如果锁在给定的等待时间内空闲且当前线程未中断,则获取锁。

三、示例代码

ReentrantLock应用场景

1 同步执行

和synchronized差不多,如果已经有线程占用,则进行排队等待

适用于资源的争抢,比如文件操作,同步消息的发送

@Slf4j
 public class Reentrant01 {
      //公平锁 默认非公平锁
     private ReentrantLock lock = new ReentrantLock(true);
     public void run() {
         try {
             // 阻塞获取锁
             lock.lock(); 
             // 模拟操作
             log.info(Thread.currentThread().getName() + " get lock");
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         } finally {
             lock.unlock();
         }
     }
     public static void main(String[] args) {
         Reentrant02 lock = new Reentrant02();
         for (int i = 0; i < 10; i++) {
             new Thread(() -> {
                 lock.run();
             }, "t" + i).start();
         }
     }
 }

当前线程释放后 其他线程才能获取到锁

2 尝试执行

如果已经被lock,则立即返回false不会等待,达到忽略操作的效果

@Slf4j
public class Reentrant02 {
    private ReentrantLock lock = new ReentrantLock();

    public void run() {
        if (lock.tryLock()) {
            try {
                // 模拟操作
                log.info(Thread.currentThread().getName() + " get lock");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        } else {
            log.info(Thread.currentThread().getName() + " abort");
        }
    }

    public static void main(String[] args) {
        Reentrant02 reentrant02 = new Reentrant02();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                reentrant02.run();
            }, "t" + i).start();
        }
    }
}

执行结果:

17:22:21.134 [t2] INFO test.Reentrant02 - t2 abort
17:22:21.134 [t4] INFO test.Reentrant02 - t4 abort
17:22:21.134 [t7] INFO test.Reentrant02 - t7 abort
17:22:21.134 [t9] INFO test.Reentrant02 - t9 abort
17:22:21.134 [t1] INFO test.Reentrant02 - t1 abort
17:22:21.134 [t8] INFO test.Reentrant02 - t8 abort
17:22:21.134 [t3] INFO test.Reentrant02 - t3 abort
17:22:21.134 [t6] INFO test.Reentrant02 - t6 abort
17:22:21.134 [t0] INFO test.Reentrant02 - t0 get lock
17:22:21.134 [t5] INFO test.Reentrant02 - t5 abort

具体场景:

• 用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发。

• 用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发)。

3 尝试等待执行

获取锁如果被占用等待一段时间

@Slf4j
public class Reentrant03 {
    private ReentrantLock lock = new ReentrantLock();

    public void run() {
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                // 模拟操作
                try {
                    log.info(Thread.currentThread().getName() + " do work");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                log.info(Thread.currentThread().getName() + " abort");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Reentrant03 lock = new Reentrant03();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lock.run();
            }, "t" + i).start();
        }
    }
}

执行结果:

17:30:07.992 [t1] INFO test.Reentrant03 - t1 do work
17:30:10.988 [t5] INFO test.Reentrant03 - t5 abort
17:30:10.988 [t0] INFO test.Reentrant03 - t0 abort
17:30:10.990 [t6] INFO test.Reentrant03 - t6 abort
17:30:10.992 [t2] INFO test.Reentrant03 - t2 abort
17:30:10.992 [t3] INFO test.Reentrant03 - t3 abort
17:30:10.992 [t4] INFO test.Reentrant03 - t4 abort
17:30:10.992 [t7] INFO test.Reentrant03 - t7 abort
17:30:10.992 [t9] INFO test.Reentrant03 - t9 abort
17:30:10.992 [t8] INFO test.Reentrant03 - t8 abort

4 可中断执行

当正在进行的操作发生中断时,释放锁,不影响其他线程锁的获取

@Slf4j
public class Reentrant04 {
    private ReentrantLock lock = new ReentrantLock();

    public void run() {
        try {
            lock.lockInterruptibly();
            // 模拟操作
            try {
                // t3线程报中断
                if ("t3".equals(Thread.currentThread().getName())) {
                    throw new InterruptedException();
                }
                log.info(Thread.currentThread().getName() + " do work");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Reentrant04 reentrant01 = new Reentrant04();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                reentrant01.run();
            }, "t" + i).start();
        }
    }
}

运行结果:


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值