ReentrantLock加锁解锁源码分析及避坑总结

本文详细分析了ReentrantLock的非公平锁加锁方法,指出在阅读源码时易遇到的陷阱,并解释了正确理解加锁流程的关键。同时,简要介绍了公平锁的加锁过程,以及解锁的实现。旨在帮助初学者避开常见误区,理解ReentrantLock的工作原理。
摘要由CSDN通过智能技术生成

一句话总结

谨以此文送给懒得思考源码的新人,包括曾经的自己。

先看构造方法,默认执行非公平锁

//先看构造方法,默认执行非公平锁
public ReentrantLock() {
	sync = new NonfairSync();
}
执行公平锁的构造方法	
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁加锁方法

final void lock() {
	//线程进来后直接利用CAS尝试抢占锁
	if (compareAndSetState(0, 1))
		//如果抢占成功state值会被改为1,且设置对象独占锁线程为当前线程
		setExclusiveOwnerThread(Thread.currentThread());
	//没有抢占成功,执行acquire(1)
	else
		acquire(1);
}
//点aquire()来到AQS,这里调用了四个方法,这里看先第一个tryAcquire(arg),其他的以后有空再细说。
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

此处深坑,用eclipse哪里不会点哪里直通坑底!
tryAcquire(arg)点击后转到AQS里的tryAcquire(arg),但此方法的结果是抛错,一脸懵逼。
其实这里有点套娃,新人和面向对象不太熟悉的人容易在这里晕车。子类NonfairSync里继承了父类AQS的acquire()方法,这个方法又调用了tryAcquire()方法,而这里的tryAcquire()方法子类已经重写,所以并不是执行父类的tryAcquire()方法,
而是调用了子类NonfairSync自己重写的tryAcquire()方法。

其实真正执行的方法就在lock()脚底下

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

顺藤摸瓜继续看

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //第一次比较,看当前能否拿到锁。
			if (c == 0) {
               //第二次比较,利用CAS抢占锁,将当前线程对象设置为执行线程,返回结果true。
			   if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
			//如果第一次比较发现锁还没释放,那么需要判断正在执行的线程是不是当前对象
            else if (current == getExclusiveOwnerThread()) {
                //如果是正在执行的线程是当前对象,那么给当前对象的锁状态+1,没报错时返回true
				int nextc = c + acquires;
				//如果锁状态是负数,说明超过最大锁值,直接抛错
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
			//如果第一次比较发现有锁并且不是当前对象持有锁,返回false.等待下一次宿命轮回。
            return false;
        }

尽管非公平锁能确保整体执行效率,但CAS的过程一将功成万骨枯,那些没抢上锁的却会CAS导致额外的资源消耗。

这里就分析完非公平锁的加锁流程。

尽管ReentrantLock源码字里行间都透着对公平锁的深深鄙视,但存在即合理,我们还是需要再看看公平锁的加锁过程。

公平锁对象第一次执行不需要CAS抢锁,而是直接执行acquire(1),在使用tryAcquire()时执行的是自己重写的方法tryAcquire。

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //这里要先确认当前是否排在队列最前,hasQueuedPredecessors()这个方法
				//用来判断当前线程在队列中的位置,如果不在队列头部返回true,反之返回false。
				if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

解锁过程:

 public void unlock() {
        sync.release(1);
    }

就此一家,别无分号。
点release方法,来到了AQS,这个tryRelease()方法是啥?

public final boolean release(int arg) {
        //这里使用tryRelease返回值arg进行判定,如果锁数不为0,则arg为false。
		//那么release()方法返回false,解锁失败,否则返回true本次解锁成功。
		if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

点tryRelease()方法,发现并不是我们需要的方法,这里又和前面那个坑类似,但知道了同样的坑咱不能掉进去两次。

protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

回到ReentrantLock的内部类Sync,发现他继承了AQS,
那么他应该继承或重写了tryRelease()方法
果然:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //如果执行线程非当前对象线程,抛错
			if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果执行线程是当前对象线程,判断解一次锁后锁状态值,
			//如果为0,结束当前线程,锁状态值变为0,返回true
			//如果不为0,锁数减1,返回false
			if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

这里基本完成了ReentrantLock加锁解锁源码分析,希望小白能够从容避坑,愉快玩耍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值