Java并发之ReentrantLock

一、什么是AQS?

AQS是AbstractQueuedSynchronizer的简称,AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。AQS实现了等待队列、条件队列、独占或共享锁的获取。而在Java中也提供了许多基于AQS实现的锁:
基于AQS实现的锁
1.1 AQS的特性
  • 阻塞的等待队列
  • 共享或独占
  • 公平或非公平
  • 可重入
  • 允许中断
1.2 AQS内部信息

AQS中是基于一个volatile修饰的变量来标识共享资源的状态,以及提供对应的读写方法:

//用于标识共享资源的状态
private volatile int state;

//对state的读方法
protected final int getState() {
    return state;
}

//对state的写方法
protected final void setState(int newState) {
    state = newState;
}

//对state的CAS写方法
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS中定义的两种队列:
同步等待队列:主要作用就是存放竞争锁失败的线程,这个队列是双端链表队列
条件等待队列:调用await()方法后会释放锁,同时线程会加入条件等待队列,并且当前线程会挂起;调用signal()会唤醒条件队列中挂起的线程,并转移到同步等待队列中。
同步等待队列数据结构:

同步等待队列

条件等待队列数据结构:
条件等待队列

AQS中定义的两种资源共享方式:
Exclusive-独占:资源只能由一个线程获取,例如ReentrantLock
Share-共享:资源可以由多个线程获取,例如Semaphore、CountDownLatch

AQS中Node的五个状态
1.值为0的时候,表示是初始状态,当前节点正在同步等待队列中等待获取锁;
2.CANCELLED = 1,表示线程已取消;
3.SIGNAL = -1,表示当前节点的后续节点可以被唤醒;
4.CONDITION = -2,表示当前节点在条件等待队列中;
5.PROPAGATE = -3,表示后续的acquireShared能够得以执行。

1.3 AQS同步等待队列

同步等待队列也叫CLH队列,是一种基于双向链表实现的队列,是FIFO。
处理流程:
1.当线程获取锁失败后,AQS会构建一个Node节点,然后存放到CLH队列中,并阻塞当前队列;
2.当持有锁的线程释放锁后,AQS会唤醒头结点,让头结点对应的线程去获取锁;
3.如果持有锁的线程调用signal()、signalAll()方法,就会将条件队列中的节点转移到同步队列。

1.4 条件等待队列

AQS中的条件等待队列是单向链表数据结构,使用nextWatier进行连接。
1.调用await方法后会释放锁,并且会阻塞当前线程,同时向Condition队列尾部添加一个节点;
2.调用signal方法后会将Condition首部的节点移动到同步队列的尾部,然后唤醒因await方法阻塞的线程。

//await signal
private static void testReentrantLock() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        Thread thread1 = new Thread(() -> {
            reentrantLock.tryLock();
            try{
                System.out.println("Thread1 get lock!");
                condition.await();
                System.out.println("唤醒后,再次执行");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            reentrantLock.lock();
            try{
                System.out.println("Thread2 get lock!");
                condition.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        });

        thread1.start();
        Thread.sleep(1);

        thread2.start();
        System.out.println("main Thread!");
    }

二、Java层面实现的ReentrantLock锁

ReentrantLock是基于AQS实现的,它的功能类似于synchronized,是一种互斥锁(独占锁),可以保证线程并发安全。

2.1 Reentrantlock的特点

1.支持线程中断
2.获取锁可以设置超时时间
3.支持公平和非公平锁
4.支持多个条件变量、条件队列
5.支持锁重入

2.2 synchronized和Reentrantlock的区别

1.synchronized是jvm层面提供的锁,Reentrantlock是Java层面提供的锁;
2.synchronized是非公平的锁,而Reentrantlock既有公平也有非公平实现;
3.synchronized不可以被中断,而Reentrantlock支持中断;
4.synchronized支持异常自动释放锁,而Reentrantlock则要在finally块手动释放

//Reentrantlock的使用
  public static void main(String[] args) throws InterruptedException {
//        ReentrantLock lock = new ReentrantLock(true); //公平锁
        ReentrantLock lock = new ReentrantLock(); //非公平锁

        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                //加锁
                lock.lock();
                try {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    //解锁
                    lock.unlock();
                }
            }, "t" + i).start();
        }
 }

三、源码分析

3.1 加锁逻辑

非公平加锁:
lock

acquire方法:
尝试加锁

tryAcquire方法最终实现:
image.png

addWaiter方法:
image.png

enq:
image.png

acquireQueued:
image.png
3.2 释放锁

release方法:
image.png

tryRelease:
image.png

unparkSuccessor:
image.png

多线程执行流程:
image.png

你可能感兴趣的:(Java并发之ReentrantLock)