从ReentrantLock开始入手AQS源码

ReentrantLock是依赖于AQS的一个锁的实现,它是一把独占锁,并且是一把可重入锁。本人看源码的功底比较差,还是只能从经常用到的东西入手。那么就开始咯。

1.构造器

//非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
}
//可以指定公平锁与否(其实两种锁的很多地方应该是相似的,因此我们先考虑非公平锁)
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}

2.生成了对象,那么我们肯定是直接开始让他干活。lock()方法。

public void lock() {
        sync.lock();
}
//sync其实就是一个内部类,继承aqs,也是一个抽象类,其实现有公平锁和非公平锁
//那么我们直接去看非公平锁了
final void lock() {
            //state是一个volatile修饰的代表锁的状态,0代表没被占用,1代表被占用
            //这里是通过cas进行占有锁
            if (compareAndSetState(0, 1))
                //如果占有成功,那么将独占线程设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //锁被占用,尝试获取锁
                acquire(1);
}

(1)当锁没有被占用的时候,那么就将锁给当前线程就完事了。

(2)锁被别的线程占用,那么就会去尝试获取锁

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
//acquires为1
//如果当前线程获取到了锁,那么就返回true 否则返回false
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //这个时候,锁被释放了,那么就通过cas设置锁被占有
            //这里也就是不公平的原因了,因为这个时候,在队列当中的线程可能还在排队,而当前线程就已
            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;       
}
//放入等待链表当中
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;
        if (pred != null) {
            node.prev = pred;
            //通过cas确保tail还是之前的tail
            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) { // 初始化尾巴
                //这里是指头节点是一个空节点
                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();//前驱节点-可以看成是获得了锁的节点
                //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源
                //这个地方应该是比较重要的,也就是说,一旦放入队列当中,没有获取到锁的话
                //那么就会一直for自旋
                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);
        }
    }

那么总结一下该方法,lock()是一把独占锁,会将占有锁的线程的状态设置为1,如果重入的话,那么就会+1,这时有其他线程的时候,会有两种手段,一是直接尝试获取锁,不用管其他排队的线程(这里就是不公平的地方);二是指将该线程放入到由链表形成的队列当中,其中的线程一直for循环自旋下去,知道获取到锁为止。
3.中国一句古话,解铃还须系铃人。那么既然锁住了,你肯定得解锁吧。那么就看看unlock()

public void unlock() {
    //读读名字,猜一下,是释放的意思
    sync.release(1);
}
public final boolean release(int arg) {
        //尝试释放锁
        //释放成功与否
        if (tryRelease(arg)) {
            //释放成功
            Node h = head;//等待队列的头
            //(1)先判断有没有等待线程
            //(2)该线程是不是处于等待状态
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}
//看看tryRelease(1)
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;
}

------------------------------------------------------------接下来一起看一下公平锁----------------------------------------------------------------------------

公平锁也就是公平的,那么意味着线程都是要排队的,那么大致猜想一下,我认为区别比较大的地方就在于tryAcquire(1)这个地方。那么我们拿点代码出来说话。

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;
}
public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        /**有点复杂 一个个讲
         * h!=t是指没有队列存在就返回false
         * ( s=h.next )==null 有队列存在,但是只有一个线程在排队
         * 当前排队的线程不是该线程
         */
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

2.接着看一下上面看漏的东西。lock的强大之处,tryLock()系列。尝试获取锁。有两个方法,一个是定时了,一个是没定时。那么先来看一下没定时的。

public boolean tryLock() {
        //可以看出就是瞬间去尝试获得锁,能够获得就返回true
        //获得失败就返回false,也就是放弃执行的意思?
        //那么就可以看成是一种降级处理了,例如多人修改文件
        //我没保存成功就算了,那么我就将该文件放到一个备份文件夹下,人工补偿
        return sync.nonfairTryAcquire(1);
}

哇,这么简单??那就再看看定时的。tryLock(long timeout, TimeUnit unit):时长,单位

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        //是看被打断没
        if (Thread.interrupted())
            throw new InterruptedException();
        //tryAcquire(1)尝试去获取锁
        //继续看后面这个doAcquireNanos(),这里猜测一下,应该是用一种计时循环的方式一直去
        //尝试获取锁
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
}

private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        //死亡倒计时
        final long deadline = System.nanoTime() + nanosTimeout;
        //将自己放入队列当中
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                //获得前面的节点
                final Node p = node.predecessor();
                //自己是老二了就能够开始尝试获取锁了
                //获取成功就完了
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //判断是否超时
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    //超时,取消获取锁,并且返回失败
                    return false;
                // 超时时间未到,且需要挂起(这个地方不是很懂)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    // 阻塞当前线程直到超时时间到期
                    LockSupport.parkNanos(this, nanosTimeout);
                //判断线程状态是否为:中断
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
扫描二维码关注公众号,回复: 3150023 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_40384690/article/details/81059753