Java同步机制-ReentrantLock使用

基本概念

ReentrantLock是自JDK1.5开始引入的一种排他锁,它提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量。我们看下ReentrantLock如何使用的,代码如下:

public class ReentrantLockDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        // 申请锁
        lock.lock();
        try {
            // 在此对共享数据进行访问
            // ...
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 总是在finally块中释放锁,以避免锁泄露
            lock.unlock();
        }
    }
}

从上述代码可以看出,使用还是挺简单的,这边需要注意一点,就是释放锁最好放在finally块中,以免异常后导致锁没释放成功,造成锁泄露。

常用方法

方法说明
lock()获取锁
lockInterruptibly如果当前线程未被中断,则获取锁
tryLock尝试获取锁,没有其它线程竞争则获取到锁并返回true,否则立即返回false
unlock释放锁
newCondition返回绑定到此Lock实例的新Condition实例

CAS

CAS全称是Compare and Swap,即比较并交换,是通过原子指令来实现多线程的同步功能。它修改数据前先从内存中读取出实际值,把它当成预期的值,当真正去修改的时候,会比较此时内存中的实际值跟刚才读取的预期值是否一致,若一致则可以修改成功,若不一致,则修改失败(说明在修改期间被其它线程修改了)。
下图展示了操作期间没有其它线程操作同个内存值:
在这里插入图片描述
下图展示了操作期间有其它线程操作同个内存值:
在这里插入图片描述

AQS

AQS(AbstractQueuedSynchronizer)即队列同步器,它是构建锁或者其他同步组件的基础框架,它是JUC并发包中的核心基础组件。AQS主要包括如下几个部分:

  • 状态变量state:AQS通过一个整数值来标识锁有没有被占用。
  • 同步队列:AQS内部维护了一个链表结构的同步队列,当线程竞争锁失败后,会被放入同步队列中。
  • Condition队列:这个队列主要用来实现等待/通知(类似synchronized的wait/notify)。
同步队列运行原理

在这里插入图片描述

  • 当只有一个线程执行时,线程被包装成node,获取到锁并插入链表头部进行执行。
  • 多个线程同时竞争锁时,未获取到锁,则被包装成node,并依次插入到阻塞队列尾部。
  • 当前拥有锁的线程执行结束后释放锁,并唤醒它后面的一个线程,被唤醒的线程重新竞争锁,获取后则执行,执行完后类似,也释放锁并唤醒后面的一个线程。
Condition队列运行原理

在这里插入图片描述

  • 当持有锁的线程,调用Condition.await方法使线程进入等待状态时,它先被插入到对应的Condition队列中(条件队列是单向链表)。
  • 插入至Condition队列中后它会释放当前锁,并唤醒阻塞队列的后一个线程,自身从阻塞队列中移除。
  • 若调用Condition.signal方法,它会唤醒一个等待在Condition上的线程,被唤醒的线程会被插入到阻塞队列的尾部,并从条件队列中删除。

ReentrantLock内部原理

我们跟踪一下ReentrantLock的源码,进入它lock方法:

  // ReentrantLock.lock()
  public void lock() {
  	  // 这个sync,是我们new ReentrantLock()时初始化的
      sync.lock();
  }

  //  无参构造函数
  public ReentrantLock() {
  	  // 默认初始化一个非公平锁
      sync = new NonfairSync();
  }

这边我们先介绍一下公平锁和非公平锁。

公平锁

公平锁的意思就是所有的线程按照请求锁的顺序依次执行(例如:一个新线程请求时,发现阻塞队列里面有其它线程排队,那么它直接插入到阻塞队列尾端进行排队)。
当我们创建ReentrantLock对象时,传入一个true参数,则创建的就是公平锁:

ReentrantLock lock = new ReentrantLock(true);
非公平锁

非公平锁就是有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的(例如:一个新线程请求,它不管阻塞列表有没有其它线程在排队,它直接去竞争锁,若竞争成功则就直接执行)。

ReentrantLock新建时,默认就是非公平锁,我们进入非公平锁NonfairSync的代码:

  static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // 获取锁方法
        final void lock() {
            if (compareAndSetState(0, 1))
                // 获取到了锁设置一下当前线程是自身
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	// 获取锁失败,则进入阻塞队列
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

进入compareAndSetState方法:

  protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

我们之前说过,AQS是通过一个state整数值(默认是0)来标识锁有没有被占用,这边就是通过CAS的方式,把state从0改为1,若修改成功说明成功获取到锁,若修改失败说明获取锁失败。修改失败进入acquire方法:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
        	// 加入阻塞队列
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

可以看出,未竞争到锁之后,就会添加至阻塞队列。这边我们就不往后深究了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值