并发编程之ReentrantLock源码分析

目录

ReentrantLock类

ReentrantLock构造方法

Sync内部类

NonfairSync类对非公平锁的实现

FairSync类对公平锁的实现

Lock接口方法的实现

Condition(条件变量)


ReentrantLock类

public class ReentrantLock implements Lock, java.io.Serializable {

    //内部属性Sync类
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
         //...
    }

    //非公平锁的实现
    static final class NonfairSync extends Sync {
         //...
    }

    //公平锁的实现
    static final class FairSync extends Sync {
         //...
    }

    public ReentrantLock() {
          sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

   实现Lock接口需要实现的方法

    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();

   从上述代码我们可以了解到,ReentrantLock类的大致结构,首先实现Lock接口,同时肯定需要实现Lock接口的一些方法,其次在ReentrantLock类中定义了一个Sync属性,是一个内部抽象静态类,同时Sync被finl修饰,表示一经定义不能再修改了。Sync继承AbstractQueuedSynchronizer类,同时定义了Lock接口并编写和重写了一些方法,NonfairSync类是对非公平锁的实现,FairSync类是对公平锁的实现。通过构造方法返回对象的不同来区分是使用那种类锁对象(后边会详细说明)。


ReentrantLock构造方法
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

        从上述代码可以看出,默认创建ReentrantLock对象,是非公平锁。传入true/false通过三木表达式可以获取不同的锁对象,默认false也是非公平锁。


Sync内部类
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

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

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

        //  剩余部分方法...
    }

主要介绍三个重要方法:

Lock(公平/非公平方式的不同实现)

       抽象方法等待子类去实现,也就是对公平/非公平在Lock方法中可以体现不同之处。

nonfairTryAcquire:(非公平锁的尝试获取,方法被final修饰已经完善,不希望破坏)

1. 如果当前锁的状态为0,表示当前没有线程持有该锁,那么尝试以CAS原子方式将锁的状态从0更新为acquires,并设置当前线程为锁的独占线程。如果更新成功,表示当前线程成功获取了锁。

2. 如果当前线程是持有锁的线程,那么直接计算新的锁状态,并更新锁的状态。这是实现可重入锁的关键,允许同一个线程多次获取锁,需要维护锁的重入次数。

3. 如果锁的状态不为0且当前线程不是持有锁的线程,则表示锁已经被其他线程持有,或者当前线程不是持有锁的线程,此时返回false表示获取锁失败。

tryRelease释放锁

1. 计算新的锁状态c,通过减去release的数量来模拟锁的释放。

2. 检查当前线程是否是持有锁的线程,如果不是,则抛出illegalMonitorStateException异常,因为只有持有锁的线程才能释放锁。

3. 如果新的锁状态c为0,表示锁将被完全释放。在这种情况下,将free标志设置为true,表示锁已经被释放。同时,将持有锁的线程设置为null,表示锁不再被任何线程持有。

4. 最后,更新锁的状态为新的值c。返回free标志,表示是否成功释放了锁。


NonfairSync类对非公平锁的实现
    //非公平锁的实现
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

        NonfairSync继承自Sync重写了Lock方法和tryAcquire方法。

        lock方法中首先尝试使用CAS方法原子性地将锁的状态从0更新为1。如果成功,表示锁被当前线程获取,然后将当前线程设置为独占锁的线程。如果失败,即锁已经被其他线程持有,就调用AQS中的acquire(1)方法进行进一步的获取操作。在acquire(1)方法中实际上是调用了非公平锁中的tryAcquire方法(上述代码中的tryAcquire方法),在该方法中调用nonfairTryAcquire方法,该方法实现了可重入性,如果还是没有获取到锁(锁被其他线程持有)会将其方入等待队列等待其他线程释放锁。

        在以上描述中,体现了非公平锁的实现,具体体现为:1. 尝试加锁时的CAS操作 2.加锁失败后的acquire中调用的tryAcquire方法。


FairSync类对公平锁的实现
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(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;
        }
    }

         FairSync继承自Sync重写了Lock方法和tryAcquire方法。

         lock方法中,直接调用AQS中的acquire方法,在该方法中会调用公平锁中的tryAcquire方法(重写的父类tryAcquire方法),拿到当前线程,尝试获取锁的状态,如果当前锁的状态c为 0,表示当前锁没有被持有。在这种情况下,它会检查是否有等待的前驱节点
(!hasQueuedPredecessors())也就是查看同步等待队列中是否有数据,如果没有尝试原子地将锁的状态从 0 设置为指定的数量acquire。如果成功设置了锁的状态,就将当前线程标记为独占锁的线程,并返回true表示获取锁成功。如果当前锁的状态不为 0,表示锁已经被持有。在这种情况下,它检查当前线程是否是独占锁的线程。如果是,就将锁的状态增加 acquires,并返回 true 表示获取锁成功。如果以上条件都不满足,表示获取锁失败,返回 false。

       在以上描述中,体现了公平锁的实现,具体体现为:通过检查锁的状态和前驱节点的情况来决定是否允许当前线程获取锁,从而确保等待时间较长的线程有机会优先获取到锁。


Lock接口方法的实现

        在实现的lock接口方法中,其实就是对公平(FairSync)/非公平(NonfairSync)两个内部类的调用。

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

    public void unlock() {
        sync.release(1);
    }

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

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

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

Condition(条件变量)
    //ReentrantLock源码
    public Condition newCondition() {
        return sync.newCondition();
    }


    //AQS源码
    final ConditionObject newCondition() {
            return new ConditionObject();
    }

   
    //AQS源码
    public class ConditionObject implements Condition, java.io.Serializable{

        public final void await() throws InterruptedException {
            
               // ...
        }

        public final void signal() {
               // ...
        }   
    }

       Condition通过sync的newCondition方法的到ConditionObject对象,ConditionObject实现了Condition接口,并实现了多个方法,主要由await/signal也就是阻塞唤醒方法。await和 signal 方法在实现使用了park和unpark方法来实现线程的阻塞和唤醒。
为什么使用park和unpark:

1. park() 和 unpark() 是 sun.misc.Unsafe 类中的方法,它们允许线程在任意位置进行阻塞和唤醒。相较于 wait() 和 notify() 这类基于监视器的方法,它们更为灵活。

2. park() 允许当前线程进入阻塞状态,而 unpark(thread) 则可以唤醒指定的线程,即使它没有处于阻塞状态。

3. 使用 park() 和 unpark() 可以避免因为等待线程和唤醒线程执行顺序问题而导致的死锁。通过直接控制线程的阻塞和唤醒,可以更精细地管理线程的状态。

       此外ConditionObject 内部会包含一个等待队列,用于管理等待条件满足的线程。当调用 await() 方法时,会释放相应的锁,线程会被加入到这个等待队列中(条件等待队列中)。而调用signal或signalAll方法时,会将条件等待队列中的第一个节点从条件等待队列中移动到同步队列中(即移动到AQS的等待队列中),表示该线程有机会再次参与锁的竞争。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值