ReentrantLock的实现原理

一、ReentrantLock

ReentrantLock可重入锁,这里我们主要分析它的加锁与解锁过程。在分析之前,先介绍几个对象。
Sync继承了AbstractQueuedSynchronizer(以下称AQS队列),在AQS队列中,有以下属性:

  • head:队头
  • tail:队尾
  • state:锁的状态,默认0
  • exclusiveOwnerThread:持有锁的线程(在AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer中)
  • Node:节点,队列中存放的元素
    • thread:node所标识的线程
    • prev:node的前一个节点
    • next:node的下一个节点
    • waitStatus:节点的一个状态,用来判断节点是否在park

二、加锁过程

先分析公平锁的加锁过程,默认是非公平的。

1、当只有一个线程t1的情况下:

public class Demo1 {
    static ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try{
                lock.lock();
                say();
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t1");
        t1.start();
    }
    private static void say(){
        System.out.println("========"+Thread.currentThread().getName());
    }
}

源码点进去,调用链为:
java.util.concurrent.locks.ReentrantLock.Sync#lock
java.util.concurrent.locks.ReentrantLock.FairSync#lock
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
1、acquire(1);

public final void acquire(int arg) {
		//如果tryAcquire返回true表示加锁成功,就不会走后面的代码了
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
			//获取当前线程
            final Thread current = Thread.currentThread();
            int c = getState();  //拿到当前锁的状态,默认是0
            if (c == 0) {
            	//hasQueuedPredecessors是判断t1是否需要去排队;如果队列中有线程在排队那么我也排队,否则我就通过cas去上锁。
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //上锁成功,将锁的线程持有者设为当前线程
                    setExclusiveOwnerThread(current);
                    //加锁成功返回true
                    return true;
                }
            }
            //如果c!=0,表示当前已经有线程持有锁,并且还没有释放;判断持有锁的线程是否等于当前线程,也就是重入锁。
            else if (current == getExclusiveOwnerThread()) {
            	//是重入锁,就更新c=c+1
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

hasQueuedPredecessors,判断队列中是否为空

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //此时head=tail=null  所以返回false
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

小结:在只有一个线程的情况下,上锁成功,不会初始化AQS队列。

2、假设在t1还未释放锁的前提下,t2来了。调用lock.lock();

前面的流程都一样,此时当我们调用tryAcquire这个方法时,此时c=1,并且锁的持有者是t1线程,所有会返回false。返回false会执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法进行排队。

private Node addWaiter(Node mode) {
		//将当前线程封装成一个node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //获取队尾node
        Node pred = tail;
        //判断队尾是否为空
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //此时的pred肯定等于null,因为我们还没有对其进行初始化。调用enq(node)进行初始化
        enq(node);
        return node;
    }

enq(node)初始化队列

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //判断队尾是否为空
            if (t == null) { // Must initialize
            	//为空,调用CAS进行初始化,head=tail
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//这里是for循环,所有第二次会进这里,因为tail已经不等于null了
            	//将t2线程所标识的node前一个node指向tail
                node.prev = t;
                //将t2线程所标识的node节点设为tail节点
                if (compareAndSetTail(t, node)) {
                	//将head节点的下一个指向t2 Node
                    t.next = node;
                    return t;
                }
            }
        }
    }

acquireQueued(node),进行排队处理

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//拿出当前节点的前一个
                final Node p = node.predecessor();
                //这里的p=head 但是tryAcquire(arg) 会返回false,因为t1还没有释放锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //shouldParkAfterFailedAcquire判断是否要进行排队
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire;判断在加锁失败后线程是否需要park;由于外层是for (;?,所以会循环调用这个方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//默认0
        if (ws == Node.SIGNAL)
        	//第二次进来的时候,waitStatus=-1 返回true
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	//第一次进行的时候进行cas,将head的waitStatus状态修改成-1
           compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;

shouldParkAfterFailedAcquire返回true,就会调用parkAndCheckInterrupt(),进行排队;

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

3、假设在t1还未释放锁的前提下,t3来了。调用lock.lock();

tryAcquire加锁会失败;进 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的addWaiter(Node.EXCLUSIVE), arg)方法

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //队尾等于t2
        if (pred != null) {
        	//当前node的前一个指向t2
            node.prev = pred;
            //将t3设置成队尾
            if (compareAndSetTail(pred, node)) {
            	//t2的下一个为t3
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

再看acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//t3的前一个是t2
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //直接调用shouldParkAfterFailedAcquire将t2所表示的node的waitStatus改为-1,然后park
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

非公平锁的加锁过程

非公平锁的加锁过程相对于公平锁来说,就是一开始的时候就尝试去加锁修改状态值,而不是直接去看是否要排队;
而一旦加锁失败,就会走和公平锁一样的逻辑;一朝排队就会永远排队。

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

三、解锁过程

解锁过程不分公平锁和非公平锁
t1释放锁,lock.unlock();

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

tryRelease(1);
很简单,获取当前锁的state减1;并判断减1之后的结果是否等于0
等于0—锁为自由锁, setExclusiveOwnerThread(null);将当前锁的线程持有者置为null
不等于0----更改state会直接返回,意味着是重入锁,重入多少次,就解锁多少次;

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

tryRelease(1);解锁成功,会继续 if (h != null && h.waitStatus != 0)判断;
判断对头是否为空----肯定不为空;
h.waitStatus != 0------此时应该等于-1;
所以满足条件,会调用unparkSuccessor(h);

private void unparkSuccessor(Node node) {
		//拿出头节点的ws
        int ws = node.waitStatus;
        if (ws < 0)
        	//小于0就通过cas置为0
            compareAndSetWaitStatus(node, ws, 0);
         //获取下一个节点t2
        Node s = node.next;
        //t2不等于null  t2.ws=-1
        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)
        	//调用unpark唤醒t2
            LockSupport.unpark(s.thread);
    }

这里会调用 LockSupport.unpark(s.thread);唤醒线程t2,所以代码会接着下面的代码代码执行
在这里插入图片描述
唤醒t2线程之后,再分析acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//t2线程唤醒后
            	//获取头节点
                final Node p = node.predecessor();
                //头节点不为空,并且加锁成功
                if (p == head && tryAcquire(arg)) {
                	
                    setHead(node);
                    //下面的是setHead(node);中的代码
                    //head = node;  设置当前t2所在的node为头节点
       		        //node.thread = null;  设置t2所在的node的thread为空;因为当前线程已经拿到锁了,所以不用再引用它了
                    //node.prev = null; 设置t2所在的node的prev为null
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //修改标识,为了parkAndCheckInterrupt方法复用,当调用lock.lockInterruptibly();时,这里可以响应Interrupt异常
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

四、总结

简单总结一下(最好能自己走一遍源码)

1、加锁过程

先判断是否有线程加锁;是否重入等情况
没有线程加锁→判断是否有线程在队列中排队;没有的话直接加锁
加锁失败→如果是第一线程就初始化队列,否在就将自己放入队尾→判断自己是不是在第二个→在的话就尝试加锁,不在或者加锁失败的修改前一个节点的ws为-1,然后park。

2、解锁过程

更新state;判断是否重入,是重入的话就直接更新state,返回false;
解锁成功→判断头节点是否为空并且头节点的ws不等于0→更新头节点的ws为0→获取第二个节点→如果第二个节点不为空→直接唤醒第二个节点所标识的线程;
唤醒之后→判断自己是不是在第二个节点→是的话就直接加锁→加锁成功→将自己设为头节点,thread指向null,prev指向null→然后返回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值