欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java并发编程--锁原理之独占锁ReentrantLock

程序员文章站 2022-05-04 20:49:06
...

独占锁ReentrantLock的原理

(1). 结构

​  ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取这个锁,其他线程尝试获取就会被阻塞并放入AQS阻塞队列中,类图结构如下

Java并发编程--锁原理之独占锁ReentrantLock

​  底层是使用AQS来实现的,根据参数来决定其内部是否公平内部类sync是NonfairSync类型的则非公平,如果是FairSync则公平

(2). 获取锁

1). void lock()方法

public void lock() {
	sync.lock();
}

// 非公平锁
final void lock() {
    if (compareAndSetState(0, 1))// 如果当前锁是*的,就直接获取,这就是竞争锁
        setExclusiveOwnerThread(Thread.currentThread());
    else// 如果锁被占有,那么去排队
        acquire(1);
}

// 公平锁
final void lock() {// 公平锁,就要排队
    acquire(1);
}

// 通用
public final void acquire(int arg) {// 同之前AQS
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 非公平锁
// 非公平性体现在如果一个线程释放了锁,在进行下一步唤醒队列中的节点之前别的线程直接竞争到了锁,那么队列中的节点会唤醒失败
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //先获取线程和锁的状态
    final Thread current = Thread.currentThread();
    int c = getState();
    // 这整个if-elseif都可以让没有进行排队直接获取锁的线程直接拿到锁
    if (c == 0) {// 如果锁空闲
        if (compareAndSetState(0, acquires)) {// 获取到锁
            setExclusiveOwnerThread(current);
            return true;
        }
    }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;
}

// 公平锁
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 不同点就在这,hasQueuedPredecessors()方法中当前线程节点有前继节点返回true,AQS队列为空或当前线程是AQS的第一个节点返回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;
}

​  非公平锁当锁*时,就可以直接获取锁,而公平锁需要去acquire()方法内部判断.

​  两种锁调用的acquire()方法都一样.

​  tryAcquire()方法有所区别,实现逻辑的区别在于公平锁在锁空闲的情况下,获取锁之前确保了当前节点是头结点后的第一个节点,从而让唤醒的节点一定是队列中排过队的.

2). void lockInterruptibly()方法

​  对中断响应的获取锁.逻辑都类似,调用的是AQS中可被中断的获取锁方法

3). boolean tryLock()方法

​  尝试获取锁,如果没获取到不会阻塞

public boolean tryLock() {
    // 之前介绍过nonfairTryzaiAcquire(),就是单纯的获取锁,之前的阻塞的逻辑是在acquire中
    return sync.nonfairTryAcquire(1);
}

4). boolean tryLock(long timeout, TimeUnit unit)方法

​  设置了时间,如果在该时间内没有获取到锁,返回false.

​  TimeUnit参数为时间粒度,直接new一个TimeUnit对象即可.

(3). 释放锁

1). void unlock()方法

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

public final boolean release(int arg) {
    // 逻辑同AQS
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    // c为本次重入成功后,锁的重入次数
    int c = getState() - releases;
    // 如果不是当前线程拿到锁,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果这次重入之后,重入次数为零,证明锁没有被任何线程重入,清空锁持有线程
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置重入次数
    setState(c);
    return free;
}

最后用一幅图加深理解

Java并发编程--锁原理之独占锁ReentrantLock