JAVA并发(五)ReentrantLock&Condition

本文详细探讨了JAVA中的ReentrantLock,包括公平锁与非公平锁的工作原理,lock()和tryAcquire()方法的区别,以及公平锁和非公平锁的实现细节。此外,还介绍了ReentrantLock的Condition条件队列,展示了如何配合使用以实现更灵活的等待机制,并对比了ReentrantLock与synchronized的区别。

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

ReentrantLock是JAVA中除synchronized外,另一种简便的管理资源锁的工具类,它还提供了Condition对象的创建,供调用者添加不同的条件队列,实现更灵活多样化的等待机制,ReentrantLock是基于AQS实现的,所以在阅读本文前需要掌握AQSJAVA并发(二)AQS,否则会看起来比较吃力。

ReentrantLock

ReentrantLock有两个构造器,可选择公平锁FairSync和非公平锁NonFairSync

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

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

公平锁与非公平锁

ReentrantLock的加锁lock()分为公平锁与非公平锁。

公平锁FairSync和非公平锁NonFairSync都继承至Sync,而Sync继承至AQS,因此,ReentrantLock是基于AQS实现的。公平锁与非公平锁的区别是lock的机制不同,而锁的释放是一致的,下方代码块截取了Sync类的加锁相关的方法。Sync提供了lock的抽象方法、非公平锁的tryAcquire方法。

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

NonfairSync&FairSync

公平锁与非公平锁的代码块如下。

// 非公平锁
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1)) // CAS竞争资源
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

// 公平锁
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;
    }
}

lock()

它们lock方法的区别就是非公平锁会先尝试CAS竞争资源,竞争失败以后才会调用acquire,而非公平锁不去竞争锁资源,直接调用acquire方法。acquire方法就是把队列加入到同步队列。

// 非公平锁的lock
final void lock() {
    if (compareAndSetState(0, 1)) // CAS竞争资源
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
// 公平锁的lock
final void lock() {
    acquire(1);
}

lock方法中,非公平锁与公平锁都会调用acquire方法,

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

JAVA并发(二)AQS中,介绍了AQS提供的资源共享方式,其中资源获取与释放的条件交给AQS的子类实现,而具体如何获取资源、如何释放资源则由AQS内部实现好了,因此:

  • Sync及其子类FairSync、NonFairSync只需要关心实现tryAcquire()&tryRelease(),同时,tryAcquire()&tryRelease()是一对独占式的资源锁的获取/释放条件,因此无论是公平锁还是非公平锁,都是独占式的。如果tryAcquire()&tryRelease()返回false,AQS会主动通过acquireQueued方法将当前线程加入同步队列,

tryAcquire()

从对lock()的分析得知,tryAcquire()用于给AQS的子类提供获取锁的灵活定义机制,如果返回false,AQS会将当前线程加入同步队列。而非公平锁的tryAcquire()实际上是父类Sync实现的,公平锁的tryAcquire()FairSync实现的。

非公平锁tryAcquire

如果资源没有被占用,尝试通过CAS占用资源,如果资源已被占用,判断是否为当前线程占用的,若是,则将state+1,即重入锁的实现。

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;
}
公平锁tryAcquire

与非公平锁唯一不同的是第5行多了一个判断!hasQueuedPredecessors(),该方法用来判断同步队列中是否有其他线程正在等待资源,只有在同步队列中没有线程时,才会允许当前线程通过CAS竞争锁资源

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

小结

公平锁与非公平锁的区别:

  • 公平锁严格遵循FIFO,获取锁资源时,会判断同步队列中是否有其他线程,没有其他线程时才会通过CAS竞争资源,否则就进入同步队列。
  • 非公平锁会直接通过CAS竞争资源,两次CAS竞争都失败时才会加入同步队列。

公平锁与非公平锁都是独占锁、可重入的。

Condition条件队列

在JUC包下,只有ReentrantLock提供了newCondition接口创建Condition对象(其他的实现在同步工具类的内部类中,程序员也无法使用)。

AQS不仅有同步队列,还有条件队列(Condition),条件队列中的线程是没有权限获取资源的,只有同步队列中的线程才有机会获取资源。条件队列是严格的FIFO的,AQS支持一个同步队列、多个条件队列,通过conditon.await()方法将当前线程加入条件队列condition,通过condition.signal()方法从条件队列condition的头节点取出一个线程放在同步队列中,condition.signal()将条件队列condition中所有节点均添加到同步队列中,从条件队列移动至同步队列的线程就有机会竞争锁资源了,最后锁资源的释放还是要通过调用AQS的tryRelease实现,而ReentrantLock中调用tryRelease的是unlock()方法。

所以我们可以按照如下的例子使用条件队列。

ReentrantLock&Condition配合使用

Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();

public void customLock(int n) {
    try {
        if(n == 1) {
        	condition1.await(); // 将当前线程添加到条件队列condition1
        } else if(n > 1) {
            condition2.await(); // 将当前线程添加到条件队列condition2
        } else {
            condition3.await(); // 将当前线程添加到条件队列condition3
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public void customUnlock(int n) {
    if(n == 1) {
        conditon1.signal(); // 从条件队列condition1中移动个线程至同步队列。
    } else if(n >1) {
        condition2.signal(); // 从条件队列condition2中移动个线程至同步队列。
    } else {
        condition3.signal(); // 从条件队列condition3中移动个线程至同步队列。
    }
    lock.unlock(); // 从同步队列释放一个
}

从上面的例子可以看出,使用condition可以很方便的将线程进行分类,根据不同的条件加入不同的条件队列。在释放时,也可以根据不同的条件决定将哪个条件队列的线程移动至同步队列,给予该线程竞争资源的资格。

Condition的原理见:JAVA并发(二)AQS

与Synchronized的区别

  1. ReentrantLock基于AQS实现,synchronized基于JMM内存模型中的最小原子操作lock、unlock实现(monitorenter、monitorexit指令),synchronized是对象锁,而AQS是基于队列与CAS实现的。
  2. ReentrantLock包含公平锁(线程严格遵循FIFO获取资源的访问权限)、非公平锁两种实现,synchronized只有非公平锁。
  3. ReentrantLock更灵活,由于基于AQS,所以它支持一个同步队列和多个条件队列,而synchronized只支持一个同步队列和一个条件队列(wait、notify)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值