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

独占锁ReentrantLock的原理

(1). 结构

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

在这里插入图片描述

​  底层是使用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.

扫描二维码关注公众号,回复: 9177387 查看本文章

​  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;
}

最后用一幅图加深理解

在这里插入图片描述

发布了141 篇原创文章 · 获赞 47 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41596568/article/details/104075987