JAVA显示锁ReentrantLock学习

本文详细介绍了JAVA的显示锁ReentrantLock,包括非公平锁NonfairSync的锁获取流程,公平锁FairSync的特点,以及锁的释放机制。通过分析锁的状态和等待队列的管理,展示了ReentrantLock如何保证线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ReentrantLock入手分析

1. 想要使用ReetrantLock给一段代码加锁

        ReentrantLock lock = new ReentrantLock();
        lock.lock();

    直接new一个锁对象调用lock方法先看一下构造方法

    private final Sync sync;
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }

2.NonfairSync的锁获取实现

    Sync是ReentrantLock的一个静态抽象类,NofairSync是这个同步对象的非公平实现。非公平锁指当有多个线程排队获取锁时不会按照线程排队的顺序,找到NofairSync类

加锁流程概括为,当前线程去获取锁,若锁被占用则进入等待队列自旋获取锁

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    Sync继承自AbstractQueuedSynchronizer,Sync提供了ReetrantLock更强大的功能不阻塞的tryLock功能,NonfairSync则实现了Sync的lock方法

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

    方法会使用CAS修改lock对象的state值为1,若lock未被其他线程持有lock的state值为0,CAS成功标记lock被当前线程持有,若lock已被其他线程持有则执行AQS的acquire方法

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

    首先尝试获取lock,这里的tryAcquire是内置对象sync的方法,NonfairSync最终定位到Sync的nonfairTryAcquire方法。若当前lock未被其他线程持有逻辑和Sync的lock方法一致,使用CAS修改ReetrantLock的state值获取lock,方法返回true。若lock被当前线程持有则将state的值+1,方法返回true。若lock被其他线程持有则方法返回false。

        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()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

    这里的nonFair体现在当一个线程来获取lock,若lock空闲线程会直接尝试获取Lock,而不是按照队列顺序获取。

    回到acquire当没有获取到lock时会从acquireQueued获取等待队列,在此之前会在Node链表尾部增加一个对象包含当前线程的引用(尾部增加对象使用的都是Unsafe的原子操作线程安全方法),接下来看亿下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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    重点看一下for循环里的逻辑,会拿到当前线程的对应Node对象,拿到他的preNode,若preNode是等待队列的头结点,尝试获取lock,若此时lock获取成功将当前线程置为头节点,清除Node绑定的thread引用(等待队列Node的head是没有线程映射,head.next才有等待的线程),将Node的watiStatus设置为-1,表示后续线程需要解除阻塞,后续进入队列的node就可以继续自旋获取lock了,这个Node其实也是lock持有者线程的映射,并将前一个空node的next指针置为null以便GC回收,最后退出acuireQueued方法。
    若当前线程映射的node不是head的next或者获取lock失败则判断pre的waitStatus状态,当为-1时且当前线程未中断则继续自选获取锁。当前一个线程waitStatus为已终止或跳过则将当前Node的prev往前指向一个Node。
    

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

    当前线程获取lock失败,并当前线程在队列中自选获取Lock也失败则会终止当前线程

3.FairSync

    公平锁的实现和非公平锁基本一致只是在tryAcquire时有所不同,除非阻塞队列为空,否则直接进入阻塞队列进入循环

    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;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

4.释放锁

    显式锁和JAVA内置锁synchronized最大的区别需要手动去释放锁,否则即使线程结束,锁也会阻塞后续获取lock的线程。使用一个显示锁一定要记得使用unlock方式进行释放

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

只有线程自己才可以释放自己持有的lock,否则会抛出IllegalMonitorStateException异常。释放锁时会将state-1当state=0时将lock持有线程的引用置为null,因此加几次锁,就需要释放几次。当锁释放成功时会将head的waitStatus的值置为0表示队列里等待线程开始能够通过自选来获取锁了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值