多线程05--ReentrantLock 原理

上一篇:多线程05--Lock_fengxianaa的博客-CSDN博客

AQS AbstractQueuedSynchronizer

抽象队列同步器:AQS内部有一个核心的变量state,int类型,代表加锁的状态。初始值是0。还有一个关键变量thread,用来记录当前加锁的是哪个线程,初始值是null

过程:

线程a 调用lock()方法进行加锁,就是用CAS将state值从0变为1,然后设置 thread=线程a

ReentrantLock是一个可重入锁,每次线程a 再次加锁就是把state的值给累加 1,别的没啥变化

这时,线程b 进来发现state已经不是0了,且“加锁线程”不是自己,所以加锁失败。

线程b 会将自己放入AQS中的一个等待队列,等线程a 释放锁之后,重新尝试加锁

线程a 在执行完自己的业务逻辑代码之后,就会释放锁!

就是将state变量的值递减1,等state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null

AQS:内部定义了获取和释放锁的抽象方法,由子类具体实现,FairSync和NonfairSync都实现了这俩方法,这是模板方法模式

1. AQS内部变量

AQS中等待队列是先进先出,本质是链表,下面变量都在:AbstractQueuedSynchronizer

//同步状态,0:未加锁,1:已加锁,2:加锁两次,3:加锁三次
//加几次,就要释放几次
private volatile int state;
//拥有锁的线程,该属性在 AQS 的父类中定义
private transient Thread exclusiveOwnerThread;

//队列的头节点
private transient volatile Node head;

//队列的尾节点
private transient volatile Node tail;

static final class Node {
	volatile Node prev; //前一个结点
	volatile Node next; //后一个结点
	volatile Thread thread; //当前结点代表的线程
	volatile int waitStatus; //等待状态,有下面几个值
    
    /** 由于线程超时或中断,导致此节点被取消 */
    static final int CANCELLED =  1;
    /** 当前结点代表的线程,在释放锁后需要唤醒next节点的线程 */
    static final int SIGNAL    = -1;
    /** condition等待 */
    static final int CONDITION = -2;
    /** 共享模式下会用到,ReentrantReadWriteLock中的读锁就是多线程共享 */
    static final int PROPAGATE = -3;
}

2. ReentrantLock 源码

// NonfairSync 下的lock方法
final void lock() {
    // 以cas方式尝试将AQS中的state从0更新为1
    if (compareAndSetState(0, 1))
        //获取锁成功,设置锁的持有者是当前线程,然后直接返回
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
//获取锁的方法
public final void acquire(int arg) {
    //tryAcquire方法由子类实现,返回false,表示加锁失败,才会执行addWaiter、acquireQueued方法
    // 选择 NonfairSync 的 tryAcquire 方法点击进去
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// NonfairSync 中的tryAcquire()只有一行,就是调用 nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {//证明没有线程加锁
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }//如果已加锁,判断枷锁线程是不是自己
    else if (current == getExclusiveOwnerThread()) {
        //是自己,state+1;
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter:其实就是将线程放入同步队列

private Node addWaiter(Node mode) {
    //根据当前线程创建一个Node节点,mode这里为null
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        //如果尾节点存在,就让新节点的prev属性,指向尾节点
        node.prev = pred;
        //设置新节点为尾节点,然后返回
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);//入队
    return node;
}

//入队,参数是新创建的节点
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { 
            //如果尾节点=null,表示同步队列是空,设置一个新的头节点,并赋值给tail
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //如果尾节点!=null,
            node.prev = t;
            if (compareAndSetTail(t, node)) {//将新创建的节点作为尾节点
                t.next = node;//原尾节点的next指向新创建的节点
                return t;
            }
        }
    }
}

acquireQueued

//参数是:新创建的节点
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//获得当前线程所在结点的前一个
            if (p == head && tryAcquire(arg)) {//p是头节点,且自己获取锁成功
                setHead(node);//设置自己是头节点
                p.next = null; // 之前的头节点的next指向null,这样p就没有地方引用了,可以回收
                failed = false;
                return interrupted;
            }
            //shouldParkAfterFailedAcquire:判断是否要阻塞当前线程,如果是true,就执行parkAndCheckInterrupt方法
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) //阻塞当前线程
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//判断是否要阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    //前一个节点的状态是SIGNAL时候,允许阻塞当前线程
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {//表示pred状态为CANCELLED
        do {
            //则一直往队列头部回溯直到找到一个状态不为CANCELLED的结点,
            //将当前节点node挂在这个结点的后面。
            //这句代码:pred = pred.prev;node.prev = pred;
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 把前一个节点的状态设置为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

3. ReentrantLock 流程

加锁

入队

阻塞

5. synchronized 和 Lock区别

 * synchronized 和 Lock区别
 *      1. synchronized是java关键字,lock是java的一个接口
 *      2. synchronized自动释放锁,lock需要结合try/finally手动释放锁
 *      3. synchronized中的代码除了抛异常不可中断,lock可以设置超时时间,进行中断
 *      4. synchronized是非公平锁,lock默认是非公平锁,但也支持公平锁
 *      5. lock通过 Condition 可以精确唤醒线程。synchronized只能唤醒一个或者全部

猜你喜欢

转载自blog.csdn.net/fengxianaa/article/details/124426852