ReentrantLock 简单理解

ReentrantLock在Java中是可重入锁
可重入锁:即递归锁,指的是在同一线程,外层函数获得锁之后,内层递归函数仍然有获得该锁的代码,但不受影响
ReentrantLock公平锁和非公平锁:
ReentrantLock锁实现了Lock接口,里面具体锁的实现使用了抽象内部类Sync。Sync有两个实现类,NonfairSync和FairSync,即公平锁和非公平锁。

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁和非公平锁定义:
公平锁是多个线程获取锁时,依次获取锁,谁排队时间长谁获取锁,符合FIFO原则;
非公平锁是谁抢到锁就是谁获取锁,先等待的可能后获取锁

非公平锁:

static final class NonfairSync extends ReentrantLock.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);
    }
}

public final void acquire(int arg) {
    // 如果在tryAcquire方法中依然获取锁失败,当前线程加入同步队列中等待
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

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

在上述代码中,线程尝试获取锁,如果获取失败,调用AQS中的acquire方法,如果依然获取失败,进入同步队列中等待。

final boolean nonfairTryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前锁的状态
    int c = getState();
    //如果锁没有被持有,就尝试获取锁,不管同步队列有没有等待的线程
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果当前线程即为持有锁的线程,获取到锁,state+1
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

在nonfairTryAcquire方法中,线程尝试获取锁,不用进行排队等待,如果获取不到,加入同步队列
在锁的释放后,在AQS队列唤醒下一个线程,被唤醒线程和新线程需要重新进行竞争锁,这时线程需要通过CAS获取锁,并不能保证被唤醒的线程可以抢到锁,因此是非公平锁。

公平锁:
在FairSync 中,与非公平锁的不同的地方在于,这里直接调用的AQS的acquire方法,没有先尝试获取锁,因为要保证公平性,就要确保同步队列中没有等待的线程时,才可以获取到锁,主要在tryAcquire中实现。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    
    final void lock() {
        acquire(1);
    }
 protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //判断当前AQS的同步队列中是否还有等待的线程
            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;
    }
}

该方法多了一个判断hasQueuedPredecessors,这个方法是判断当前AQS的同步队列中是否还有等待的线程如果有,返回true,否则返回false。
当队列中没有等待的线程时,当前线程才能尝试通过CAS的方式获取锁,否则就让这个线程去队列后面排队,以此来保证公平。

公平锁的效率不如非公平锁,但可以减少饥饿发生概率。
可重入锁:
ReentrantLock的实现基于队列同步器AQS,ReentrantLock的可重入功能基于AQS的同步状态:state,通过修改AQS中state的值来同步锁的状态。,通过这个方式,来实现可重入。
当锁没有被持有时,state为0,当某一线程获取锁后,state+1,并记录下当前持有锁的线程,再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,state再+1,如果不是,则阻塞线程,
当线程释放锁时,state-1,当state值减为0时,表示当前线程彻底释放了锁,然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁。

final boolean nonfairTryAcquire(int acquires) {	
	final Thread current = Thread.currentThread();
	// 获取state的值
	int c = getState();
	// 如果state的值等于0,当前没有线程持有锁 将state的值改为1,修改成功,则成功获取锁,并设置当前线程为持有锁的线程,返回true
	if (c == 0) {
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}

	// 判断当前线程是否等于持有锁的线程,如果等于,将state的值+1,并设置到state上,获取锁成功,返回true
	 如果不是当前线程,获取锁失败,返回false
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) 
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	//如果不是当前线程,获取锁失败,返回false
	return false;
}

释放锁:

public void unlock() {
    // 调用AQS的release方法释放资源
    sync.release(1);
}
public final boolean release(int arg) {    
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 成功释放锁后,唤醒同步队列中的下一个节点,使之可以重新竞争锁           
            unparkSuccessor(h);
        return true;
    }
    return false;
}

在锁的释放过程中,调用AQS的release方法,让队列在释放完成一个锁后,立即唤醒同步队列下一个节点去竞争锁

你可能感兴趣的:(ReentrantLock 简单理解)