ReentrantLock来认识AQS、CAS、公平锁、非公平锁、独占锁

ReentrantLock实现了Lock接口
ReentrantLock与sync是组合关系。ReentrantLock中,包含了Sync对象;而且,Sync是AQS的子类;
Sync有两个子类FairSync(公平锁)和NonFairSync(非公平锁)

ReentrantLock是一个独占锁,至于它到底是公平锁还是非公平锁,就取决于sync对象是"FairSync的实例"还是"NonFairSync的实例"。

reentrantLock = new ReentrantLock(false);//创建非公平锁
reentrantLock = new ReentrantLock(true);//创建公平锁

下面介绍公平锁实现原理

ReentrantLock reentrantLock = new ReentrantLock(true);
reentrantLock.lock();//获取锁
reentrantLock.unlock();//释放锁

先看下Node元素的属性

CLH同步队列节点基本单元

static final class Node {
    /**
     * 共享
     */
    static final Node SHARED = new Node();
    /**
     * 独占
     */
    static final Node EXCLUSIVE = null;
    /**
     * 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
     */
    static final int CANCELLED = 1;
    /**
     * 表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL
     */
    static final int SIGNAL = -1;
    /**
     * 表示结点等待在Condition上,等待获取同步锁(独占锁用不上)
     */
    static final int CONDITION = -2;
    /**
     * 表示下一次共享式同步状态获取将会无条件地传播下去(独占锁用不上)
     */
    static final int PROPAGATE = -3;//传播

    /** 等待状态 */
    volatile int waitStatus;

    /** 前驱节点 */
    volatile Node prev;

    /** 后继节点 */
    volatile Node next;

    /** 获取同步状态的线程 */
    volatile Thread thread;

    /** 下一个等待的节点 */
    Node nextWaiter;

    /** 判断下一个等待的节点是否是共享式 */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
}

一、获取锁

(01) “当前线程”首先通过tryAcquire()尝试获取锁。获取成功的话,直接返回;尝试失败的话,进入到等待队列排序等待(前面还有可能有需要线程在等待该锁)。

(02) “当前线程”尝试失败的情况下,先通过addWaiter(Node.EXCLUSIVE)来将“当前线程”加入到"CLH队列(非阻塞的FIFO队列)"末尾。CLH队列就是线程等待队列。

(03) 再执行完addWaiter(Node.EXCLUSIVE)之后,会调用acquireQueued()来获取锁。由于此时ReentrantLock是公平锁,它会根据公平性原则来获取锁。

(04) “当前线程”在执行acquireQueued()时,会进入到CLH队列中休眠等待,直到获取锁了才返回!如果“当前线程”在休眠等待过程中被中断过,acquireQueued会返回true,此时"当前线程"会调用selfInterrupt()来自己给自己产生一个中断。至于为什么要自己给自己产生一个中断,后面再介绍。

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

final void lock() {
    acquire(1);
}

public final void acquire(int arg) {
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
		selfInterrupt();
	}  
}
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();//独占锁的状态
    if (c == 0) {
        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 判断线程需不需要排队

注意:独占锁的状态的和Node线程的状态是2个状态。

这里先说一下hasQueuedPredecessors()方法的作用,主要是用来判断线程需不需要排队,因为队列是FIFO的,所以需要判断队列中有没有相关线程的节点已经在排队了。有则返回true表示线程需要排队,没有则返回false则表示线程无需排队。

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

hasQueuedPredecessors方法源码看起来不是很好理解,可以参考这个hasQueuedPredecessors()

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
    //enq(node) 多次尝试设置到尾节点
}

//AQS通过“死循环”的方式来保证节点可以正确添加,只有成功添加后,当前线程才会从该方法返回,否则会一直执行下去。
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            //p == head 说明node就是队列中的第二个节点,有资格去获取锁,所以通过tryAcquire尝试去获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //执行到这说明获取失败,说明第一个节点还没释放锁,这个时候就要去等待。
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
            	interrupted = true;
            }
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//等待也是有条件的,判断node的上一个节点状态是否是SIGNAL如果是,那我node就可以去等待了,如果前一个节点的状态>0说明,上一个节点被中断,那么就需要往前推,找到正常的节点,找到正常的之后就会把node的上一个节点状态置为SIGNAL。注意此时Node节点的waitStatus还是0,只有等Node后面添加一个Node的时候,进入到这个方法才会给Node设置SIGNAL值。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;//返回false之后,就又回到了上面的for循环里面,一般都是通过ws == Node.SIGNAL成立返回true
}

//
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//线程状态变为等待,代码执行到这就卡住了。直到有线程唤醒自己。
    return Thread.interrupted();
}
//Thread.interrupted() 测试当前线程是否被中断(检查中断标志)并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。

回到上面的主入口acquire方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    	selfInterrupt();
    }
}

static void selfInterrupt() {
    Thread.currentThread().interrupt(); 
}
//interrupt() 其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。

二、释放锁

public void unlock() {
    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;
}
//独占式同步状态释放
protected final boolean tryRelease(int releases) {
    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;
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0){
    	compareAndSetWaitStatus(node, ws, 0);
    }
      
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0){
            	s = t;
            }     
    }
    if (s != null){
    	LockSupport.unpark(s.thread);//唤醒等待的目标线程
    }   
}

释放锁可以参考:释放锁

本文转载:https://blog.csdn.net/weixin_33550394/article/details/114599128

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值