通过JMH测试公平锁与非公平锁

本文通过JMH测试对比公平锁(Fair Lock)和非公平锁(Non-Fair Lock)在ReentrantLock中的性能,结果显示非公平锁在吞吐量上优于公平锁,但可能导致线程饥饿。关键在于公平锁会先检查等待队列,而非公平锁则立即尝试获取。
摘要由CSDN通过智能技术生成

在这里插入图片描述

JMH测试

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 5)
@State(Scope.Group)
public class FairAndUnfairTest {

    @Param({"1", "2"})
    private int type;

    private static Lock lock;

    @Setup
    public void setup() {
        switch (type) {
            case 1:
                lock = new ReentrantLock(true);
                break;
            case 2:
                //默认就是非公平锁
                lock = new ReentrantLock(false);
                break;
            default:
                throw new IllegalArgumentException("illegal lock type.");
        }
    }

    @Benchmark
    @GroupThreads(5)
    @Group("lock")
    public void put() {
        lock.lock();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private int randomIntValue() {
        return (int) Math.ceil(Math.random() * 600000);
    }

    @Benchmark
    @GroupThreads(5)
    @Group("lock")
    public Integer get() {
        lock.lock();
        try {
            Thread.sleep(10);
            return randomIntValue();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return 0;
    }

    public static void main(String[] args) throws RunnerException {
        Options opts = new OptionsBuilder()
                .include(FairAndUnfairTest.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(opts).run();
    }
}

读写分别五个线程,可以看出结果非公平锁性能高于公平锁

Benchmark                   (type)  Mode  Cnt        Score         Error  Units
FairAndUnfairTest.lock           1  avgt   10   113303.572 ±     826.288  us/op
FairAndUnfairTest.lock:get       1  avgt   10   113294.981 ±     843.621  us/op
FairAndUnfairTest.lock:put       1  avgt   10   113312.163 ±     813.306  us/op
FairAndUnfairTest.lock           2  avgt   10  1668873.612 ± 3378209.364  us/op
FairAndUnfairTest.lock:get       2  avgt   10  1981826.499 ± 4488348.226  us/op
FairAndUnfairTest.lock:put       2  avgt   10  1355920.726 ± 2861627.232  us/op

代码分析

ReentrantLock 默认采用非公平锁,除非在构造方法中传入参数 true 。

//默认
public ReentrantLock() {
    sync = new NonfairSync();
}
//传入true or false
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁的 lock 方法:

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
            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;
    }
}

我们可以看到,在注释1的位置,有个!hasQueuedPredecessors()条件,意思是说当前同步队列没有前驱节点(也就是没有线程在等待)时才会去compareAndSetState(0, acquires)使用CAS修改同步状态变量。所以就实现了公平锁,根据线程发出请求的顺序获取锁。

非公平锁的lock方法

static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //3.这里也是直接CAS,没有判断前面是否还有节点。
        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;
}

非公平锁的实现在刚进入lock方法时会直接使用一次CAS去尝试获取锁,不成功才会到acquire方法中,如注释2。而在nonfairTryAcquire方法中并没有判断是否有前驱节点在等待,直接CAS尝试获取锁,如注释3。由此实现了非公平锁。

总结

非公平锁和公平锁的两处不同:

  1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。

  2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值