多线程并发编程8-ReentrantLock源码剖析

    今天来说一说ReentrantLock类。ReentrantLock是可重入的独占锁,同时只能有一个线程获取该锁,其他获取该锁的线程就会被阻塞挂起。

    ReentrantLock内部最为核心的就是Sync实例,内部类Sync继承AbstractQueuedSynchronizer,又根据公平锁还是非公平锁实现对应的FairSync类和NonfairSync类。ReentrantLock创建公平锁的方式:ReentrantLock lock =new ReentrantLock(true)。ReentrantLock创建非公平锁的方式:ReentrantLock lock =new ReentrantLock(false),参数不传,默认为非公平锁。

    ReentrantLock通过操作AQS中的state状态值来实现可重入。默认情况,state为0,表示没有任何线程获取到锁。当一个线程获取该锁,通过CAS尝试将state值设置为1,如果CAS设置成功则该线程获取到锁,被记录该锁的持有者为该线程。当该线程再次获取该锁的时候,state值会被设置为2,从而实现了可重入。在该线程释放锁的时候,会尝试将state值减1,减1后状态值为0则当前线程释放该锁。

    下面通过ReentrantLock内部的源码来进一步加深对ReentrantLock获取锁、释放锁的过程。

lock()

    获取锁。

public void lock() {

sync.lock();

}

    获取锁方法只简单调用了sync.lock()方法,通过公平锁和非公平锁会调用各自具体的实现。下面会对公平锁对应的FairSync类的实现和非公平锁对应的NonfairSync类的实现进行分别说明。

NonfairSync-lock()

final void lock() {

//(1)

if (compareAndSetState(0, 1))

//(2)

setExclusiveOwnerThread(Thread.currentThread());

else

        acquire(1);//(3)

}

(1)通过CAS尝试将state状态值修改为1.

(2)通过CAS成功将state状态值修改为1则说明当前线程获取到锁,该锁记录当前线程。

(3)通过CAS失败,则会调用acquire方法,内部先调用tryAcquire方法,尝试获取资源,尝试获取失败则将当前线程插入到AQS阻塞队列中并阻塞挂起,具体的实现看“AQS源码剖析”这篇文章。

    在“AQS源码剖析”这篇文章说到tryAcquire方法是根据上层的不同机制实现各自的tryAcquire方法,下面就来看看NonfairSync类是如何实现的?

NonfairSync-tryAcquire(int acquires)

    protected final boolean tryAcquire(int acquires) {

        return nonfairTryAcquire(acquires);

}

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

    int c = getState();

//(1)

    if (c ==0) {

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

        }

}

else if (current == getExclusiveOwnerThread()) {//(2)

int nextc = c + acquires;

        if (nextc <0)// overflow

            throw new Error("Maximum lock count exceeded");

        setState(nextc);

return true;

    }

//(3)

return false;

}

(1)获取state状态值,如果等于0,说明当前锁没有线程获取,通过CAS尝试设置state,设置成功则将当前线程记录到锁内部。这里没有判断是否有比当前线程更早请求锁的线程,而是使用了抢夺策略。

(2)获取state状态值,如果不等于0,说明当前锁已经有线程获取,如果获取该锁的线程就是当前线程,则将state进行加一,实现可重入。

(3)(1)中CAS失败或(2)中锁记录的线程不是当前线程,则返回false,之后会添加到AQS阻塞队列中并阻塞挂起。

FairSync-lock()

final void lock() {

    acquire(1);

}

    FairSync类中的lock方法只是简单的调用了AQS的acquire方法,重点是在FairSync中实现的tryAcquire方法。

FairSync-tryAcquire(int acquires)

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

        int c = getState();

        if (c ==0) {

//(1)

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;

    }

}

(1)查看源码发现FairSync-tryAcquire(int acquires)方法和FairSync-tryAcquire(int acquires)方法的实现差不多,就是FairSync-tryAcquire(int acquires)方法多了一个hasQueuedPredecessors函数调用,该函数用来判断当前AQS阻塞队列中是否存在阻塞的Node,公平锁实现公平就是通过判断当前AQS阻塞队列中是否存在阻塞的Node,让先阻塞的Node先进行获取锁。

unlock()

    释放锁。

public void unlock() {

sync.release(1);

}

    释放锁方法只简单调用了sync.release(1)方法,公平锁和非公平锁的释放逻辑是一致的。

release(int arg)

public final boolean release(int arg) {

//(1)

if (tryRelease(arg)) {

Node h =head;

        if (h !=null && h.waitStatus !=0)

//(2)

unparkSuccessor(h);

return true;

    }

return false;

}

(1)尝试设置state值。

(2)设置state值成功之后,则在AQS阻塞队列从head往tail,唤醒第一个非CANCELLED状态的线程。  

tryRelease(int releases)

protected final boolean tryRelease(int releases) {

int c = getState() - releases;

//(1)

    if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

    boolean free =false;

//(2)

    if (c ==0) {

free =true;

        setExclusiveOwnerThread(null);

    }

//(3)

setState(c);

    return free;

}

(1)当前线程不等于锁记录的线程则会报IllegalMonitorStateException异常,释放锁会报IllegalMonitorStateException异常,是因为未获取锁的线程调用了unlock方法导致的。

(2)如果state减去releases值之后等于0,说明可以释放当前锁,将当前锁的记录线程设置为null。

(3)设置state值。

  今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值