Java---ReentrantLock

一.AQS

1.概述

AQS(AbstractQueuedSynchronizer),是抽象队列同步器,其实就是一个用来构建锁和同步器的框架。内部实现的关键是:先进先出的队列、state状态。

2.核心思想

AQS核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,AQS使用一个voliate int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

AQS定义了两种资源获取方式:独占(只有一个线程能访问执行)和共享(多个线程可同时访问执行)。

二.ReentrantLock和AQS的关系

在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建。

ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

三、ReentrantLock(可重入锁)

1.概述

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。

2.ReentrantLock获取锁定有四种方式

  • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
  • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
  • tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
  • lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到获取锁定,或者当前线程被别的线程中断

示例

private Lock lock = new ReentrantLock();
 
public void test(){
    lock.lock();
    try{
        doSomeThing();
    }catch (Exception e){
        // ignored
    }finally {
        lock.unlock();
    }
}

3.ReentrantLock源码解析

(1)new ReentrantLock() 实例化

构造函数有两个,无参数的构造函数默认是非公平锁;有参数的构造函数,如果传入的参数是true,则是公平锁,传入false,则是非公平锁。

    // 重入锁默认采用非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    // true:公平锁  false:非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock的公平锁和非公平锁都继承Sync,Sync又继承AQS,因此可重入锁的底层采用的AQS来实现的。 

(2)ReentrantLock调用Sync的lock方法并根据公平或非公平的lock逻辑执行

示意图:

 

源码:

public class ReentrantLock implements Lock, java.io.Serializable {
	...

    public void lock() {
        // 分为公平锁的lock()实现和非公平锁的lock()实现
        sync.lock();
    }

	// 公平锁
    static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }
    }

	// 非公平锁
    static final class NonfairSync extends Sync {
        final void lock() {
            // 【重点】使用CAS将AQS.state置为1,表示已抢占该锁,否则失败进入else
            if (compareAndSetState(0, 1)) {
                // 将AbstractOwnableSynchronizer.exclusiveOwnerThread置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            } else {
                // 走公平锁的流程
                acquire(1);
            }
        }
    }
    ...
}

非公平锁在抢占失败后,和公平锁一样,执行acquire(1)

    public final void acquire(int arg) {
        /**
         * tryAcquire(1): 判断当前线程是否成功的抢占锁
         * addWaiter(Node.EXCLUSIVE): 构建一个独占模式节点Node,并维护好该节点的前后指针Node
         * acquireQueued(addWaiter(Node.EXCLUSIVE), 1):
         */
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
            // 设置当前线程的中断标识
            selfInterrupt();
        }
    }

(3)执行tryAcquire方法去尝试获取锁

  • 公平锁
        /**
         * 进行抢锁操作,返回是否成功
         * 抢占成功条件:
         * case1> 没人抢占锁,线程A执行抢占锁操作,执行成功。
         * case2> 有人已经抢占了这个锁,但是抢占这个锁的线程就是线程A自己,那么对自己重入加锁,执行成功。
         *
         * true:抢占到了锁  false:没有抢到锁
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            /** 如果c == 0,说明可以抢占锁 */
            if (c == 0) {
                /** 如果线程不需要排队 并且 抢占锁成功(即:如果state=0,则将该值修改为1,CAS操作成功)*/
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                	// 设置抢到锁的线程为current
                    setExclusiveOwnerThread(current); 
                    return true;
                }
            }
            /** 如果c != 0,判断,是否是重入操作(即:锁本来就是被自己抢占的,支持多次抢占。) */
            else if (current == getExclusiveOwnerThread()) {
            	// 相当于state+1
                int nextc = c + acquires;
                if (nextc < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                setState(nextc);
                return true;
            }
            return false;
        }
  • 非公平锁
        /**
         * 进行抢锁操作,是否抢到非公平锁
         *
         * 处理内容:
         * 1>如果抢到锁,返回true
         *   1.1>如果当前线程第一次抢到锁:
         *        AQS.status由0变为1
         *        AQS.exclusiveOwnerThread=Thread.currentThread()
         *        返回true
         *   1.2>如果当前线程再次抢到锁(重入加锁):
         *        AQS.status++
         *        返回true
         * 2>如果没抢到锁,返回false
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) { 
                /**
                 * 获得当前独享线程,如果就是当前线程,那么执行重入操作
                 * 执行tryLock()时:
                 *      如果第二次进入,则nextc = 0 + 1 = 1
                 *      如果第三次进入,则nextc = 1 + 1 = 2
                 *      如果第四次进入,则nextc = 2 + 1 = 3
                 */
                int nextc = c + acquires;
                // overflow 溢出
                if (nextc < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                setState(nextc);
                return true;
            }
            // nf-eg—2:线程B 返回false
            return false;
        }

(4)addWaiter方法

(5)acquireQueued方法

(6)lock.unlock() 解锁

 

4.ReentrantLock加锁和释放锁的底层原理

(1)初始状态:

AQS对象内部有一个核心的变量叫做state,是用voliate修饰的int类型,代表了加锁的状态,初始状态下,这个state的值是0,表示没有加锁。

另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

(2)有一个线程调用ReentrantLock的lock()方法尝试进行加锁

当线程1尝试加锁,如果当前state=0,直接通过CAS将state=1,设置成功后,设置当前加锁线程为当前线程(线程1),此时,线程1就获得了锁。

可重入锁:如果一个线程尝试加锁,发现state!=0,判断加锁线程是否为自己,如果为自己,就可以可重入加锁,将state+1即可。

(3)加锁失败情况

当线程2尝试加锁,发现state!=0,并且当前加锁线程不是自己,则加锁失败,将自己放入到等待队列中。

(4)释放锁

当线程1全部执行完后,就会释放锁。释放锁就是将AQS内的state值递减1,直到state=0,释放锁,加锁线程设置成null。

此时,等待队列中的线程被唤醒,将state设置成1,加锁线程设置成自己,并从等待队列中出队列。

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值