IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 并发编程07-线程安全解决方案之ReentrantLock锁 -> 正文阅读

[Java知识库]并发编程07-线程安全解决方案之ReentrantLock锁

线程安全解决方案之ReentrantLock

Lock接口介绍

Lock是J.U.C包下面提供的一个接口,也是用来实现线程同步的一种解决方案,他提供了一个规范,定义了关于一个锁应该具备的能力,定义了加锁、解锁等基本的方法。实现类有ReentrantLockReadLock(在ReentrantReadWriteLock中做读锁)、WriteLock(在ReentrantReadWriteLock中做写锁)、Segment(在ConcurrentHashMap)中做分段锁。当然我们也可以通过实现这个接口去自定义我们的锁。因此,我们在来理解一下接口的意义,其实就是定义规范,定义如果你要实现一个锁,则必须按照我的规范来。

我们一起看下Lock接口定义了那些方法要我们去实现:

public interface Lock {
    
    /**
     * 线程阻塞的一种加锁方案,调用该方法用来获取锁,如果获取不到,线程会进入休眠状态,直到获取到锁为止
     */
    void lock();
    
    /**
     * 在上面方法的基础上加了中断机制
     */
    void lockInterruptibly() throws InterruptedException;
    
    /**
     * 尝试获取锁,如果锁可以用,立即获取锁,返回true,如果锁不可用,返回false
     */
    boolean tryLock();
    
    /**
     * 在上面方法的基础上加了超时机制,就是说如果不能立即获取到锁,允许多尝试几次,
     * 如果在指定的时间内获取到锁就返回true,如果时间到了还没获取到,在返回false
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    /**
     * 解锁,释放锁资源
     */
    void unlock();
       
    /**
     * 为锁绑定一个条件,这个可以用来实现wait/notify类似的线程通信的效果,后续展开
     */
    Condition newCondition();
}

ReentrantLock如何使用

ReentrantLock是我们最常用的Lock接口的一种实现,Reentrant是可重入的概念(后面展开)。他和synchroized关键字都是悲观锁。我们一起看下如何使用:

lock() and unlock()

Lock lock = new ReentrantLock();

public void sellTicket() {
    try {
        lock.lock();
        if (totalTicket > 0) {
            System.out.println("totalTicket :" + totalTicket + " 被线程: " + Thread.currentThread().getName() + " 减1");
            Thread.sleep(1000);
            totalTicket--;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 切记切记,这里一定要记得在finally里释放锁,否则会导致锁资源被占用,其他线程无法始终无法获取锁,永远被阻塞
        lock.unlock();
    }

}

错误写法1:如果代码在第2行到第6行之间报了错,抛出异常,会导致无法走到第8行,锁无法释放

lock.lock();
if (totalTicket > 0) {
    System.out.println("totalTicket :" + totalTicket + " 被线程: " + Thread.currentThread().getName() + " 减1");
    Thread.sleep(1000);
    totalTicket--;
}
lock.unlock();

错误写法2:多个线程会加载自己私有的线程栈,lock作为局部变量是每个线程栈内部私有的,不共享,会导致有多少个线程访问,就有多少锁,没办法起到互斥的作用,如果不理解那些是线程私有的,可以参考:并发编程02-什么是线程安全以及Java虚拟机中哪些数据是线程共享的,那些是线程私有的

public void sellTicket() {
    Lock lock = new ReentrantLock();
    try {
        lock.lock();
        if (totalTicket > 0) {
            System.out.println("totalTicket :" + totalTicket + " 被线程: " + Thread.currentThread().getName() + " 减1");
            Thread.sleep(1000);
            totalTicket--;
        }
        lock.unlock();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

tryLock()

tryLock()尝试获取锁,如果获取不到,线程就不进行处理。之前做一个定时任务的需求,由于项目中没有分布式任务调度框架,故用了spring自带的定时任务。这就导致如果你的应用部署在多个节点上,那么到了设定的时间,三个节点会同时触发定时任务,实际上只需要触发一次即可。当时用数据库做分布式锁实现了Lock接口,当定时任务触发的时候,先调用tryLock()方法获取锁,获取到则触发定时任务,否则什么都不做。

public void sellTicket() {
    if (lock.tryLock()) {
        try {
            // 获取到锁的分支
        } finally {
            lock.unlock();
        }
    } else {
        // 获取不到锁的分支
    }
}

错误写法:如果有多个线程访问这个方法,同一时刻只有线程1获取到了锁,线程2没有获取到锁,那么线程2会走到第5行,尝试释放锁,而实际线程2并没有获取到锁,导致代码报错。

public void sellTicket() {
    if (lock.tryLock()) {
        // 执行获取到锁的业务逻辑
    }
    lock.unlock();
}

简单说明ReentrantLock如何实现加锁/解锁

Lock是一个接口,定义了锁的规范,提供了加锁/解锁的基本方法。而具体的核心逻辑是在J.U.C包下面有一个抽象类AbstractQueuedSynchronizerAQS),在这个抽象类里面结合模板方法设计模式实现了lockunlock()等基础功能,其他锁实现只需要继承这个抽象类,在做一些自定义的实现即可。通过这个设计可以进一步理解接口和抽象类的区别:接口定义规范(can-do),抽象类提取子类的公共逻辑进行实现,减少冗余代码(is-a)。关于AbstractQueuedSynchronizer的细节可以参考:通过ReentrantLock和Semaphore看AQS独占锁和共享锁的源码。这里我们在简单提一下基本加锁流程:

  1. AQS中定义了一个全局变量:state,初始化为0,如果有线程获取锁资源,则通过CAS的原子操作对state加一,表示加锁成功
  2. 当线程释放锁资源的时候,将state重新设置为0,表示解锁成功
  3. 如果同时有多个线程争抢锁资源,同时只有一个线程获取到了锁,其他的线程如何处理?AQS提供了一个用双向链表实现的同步队列,让其他线程去这个队列里面排队,当锁被释放后,从队列里面取出一个线程获取锁
  4. 假设线程2来获取锁资源,此时线程1刚好释放了锁,他则直接加锁成功,而没有去上面说的队列中排队等待,那么这种锁我们叫做非公平锁,通过new ReentrantLock(false)定义非公平锁,默认非公平
  5. 假如线程2来获取锁资源没有插队,而是乖乖去队列里面排队等候,那么这种锁我们叫做公平锁,通过new ReentrantLock(true)定义公平锁

在这里插入图片描述

如何理解Reentrant的概念

Reentrant,英语是可重入的意思,ReentrantLock即可重入锁,synchroized也是可重入锁(用法参考:并发编程04-线程安全解决方案之如何正确使用synchroized关键字)。什么是可重入锁呢,以下面的代码为例,如果某一个线程在调用method1的时候获取到了锁,那么在调用method2的时候也会自动获取锁,即可重入。假设不会自动获取,也就是不可重入,那么下面这两段代码就会这样运行:线程1执行method方法获取了锁,接着调用method2,又需要获取锁,那么此时锁被谁占用呢?线程1,因此他会等线程1释放锁,而线程1能释放锁嘛,不能,因为他还没有执行完,他在method2中等待获取锁。这就卡住了一个bug,导致了死锁。因此我认为可重入锁在一定程度上解决了死锁问题。

Lock lock = new ReentrantLock(true);

public void method1() {

    try {
        lock.lock();
        method2();
    } finally {
        lock.unlock();
    }

}

public void method2() {
    try {
        lock.lock();
        // 方法2的逻辑
    } finally {
        lock.unlock();
    }
}
public synchronized void method1() {
    method2();
}

public synchronized void method2() {

}

Lock锁和synchroized锁的区别

相同点:

  1. 他们都可以实现线程同步,都是悲观锁
  2. 都是可重入锁

不同点:

  1. synchroized是JVM层面实现的,lock是基于AQS框架实现的
  2. lock支持公平锁和非公平锁两种,synchroized是非公平的
  3. synchroized加锁解锁是自动的,lock需要手动去做,因为手动,则更加灵活,你可以在任意地方加锁解锁,但是一定要注意合理正确的释放锁,否则会造成死锁
  4. lock提供了tryLock()方法,这个是synchroized做不到的
  5. synchroized可以结合wait/notify机制实现线程通信,而lock锁可以用Condition实现线程通信
  6. lock接口下有读锁、写锁的实现类,用来为读写锁实现高性能的读写分离的锁,synchroized是做不到的
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:26:14  更:2022-05-09 12:29:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:02:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码