Java 中怎么实现公平锁与非公平锁

发布时间:2021-07-02 14:03:43 作者:Leah
来源:亿速云 阅读:335
# Java 中怎么实现公平锁与非公平锁

## 一、锁的公平性概述

在多线程编程中,锁的公平性(Fairness)是指线程获取锁的顺序是否严格按照请求顺序分配。Java中的`ReentrantLock`提供了公平锁与非公平锁两种实现方式:

- **公平锁(Fair Lock)**:按照线程请求锁的顺序分配,先到先得
- **非公平锁(Nonfair Lock)**:允许插队,后请求的线程可能先获取锁

```java
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true); 

// 创建非公平锁(默认)
ReentrantLock nonfairLock = new ReentrantLock(); 

二、底层实现原理

1. AQS 核心结构

两种锁的实现都基于AbstractQueuedSynchronizer(AQS)框架:

public class ReentrantLock implements Lock {
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 实现锁的核心逻辑
    }
    
    // 非公平锁实现
    static final class NonfairSync extends Sync { /*...*/ }
    
    // 公平锁实现
    static final class FairSync extends Sync { /*...*/ }
}

2. 关键差异点

特性 公平锁 非公平锁
获取锁顺序 严格FIFO 允许插队
吞吐量 较低 较高
线程饥饿 不会 可能发生
实现复杂度 较高 较低

三、源码级实现分析

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) throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

2. 公平锁实现

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()) {
        // ...同非公平锁...
    }
    return false;
}

四、性能对比测试

1. 测试代码示例

public class LockBenchmark {
    private static final int THREADS = 10;
    private static final int CYCLES = 100000;
    
    public static void testLock(ReentrantLock lock) {
        long start = System.currentTimeMillis();
        ExecutorService exec = Executors.newFixedThreadPool(THREADS);
        
        IntStream.range(0, CYCLES).forEach(i -> {
            exec.submit(() -> {
                lock.lock();
                try {
                    Thread.sleep(1);
                } finally {
                    lock.unlock();
                }
            });
        });
        
        exec.shutdown();
        while(!exec.isTerminated());
        System.out.println("Cost: " + (System.currentTimeMillis()-start));
    }
}

2. 典型测试结果

锁类型 线程数 操作次数 耗时(ms)
公平锁 10 100,000 3200
非公平锁 10 100,000 1800

五、应用场景建议

适合使用公平锁的场景

  1. 需要保证线程执行顺序的业务逻辑
  2. 临界区执行时间较长的操作
  3. 对延迟敏感的系统

适合使用非公平锁的场景

  1. 高并发、高性能要求的场景
  2. 临界区执行时间极短(<100μs)
  3. 能容忍短暂饥饿的情况

六、扩展知识

1. synchronized的公平性

内置锁synchronized本质上是非公平锁,没有提供公平策略选项

2. 其他公平性实现

// 公平的Semaphore实现
Semaphore fairSemaphore = new Semaphore(1, true);

// 公平的读写锁
ReentrantReadWriteLock fairRwLock = 
    new ReentrantReadWriteLock(true);

七、常见问题解答

Q1: 为什么默认使用非公平锁?

因为在大多数场景下,非公平锁的吞吐量比公平锁高40%-100%

Q2: 公平锁如何避免饥饿问题?

通过维护一个FIFO队列,严格按顺序唤醒等待线程

Q3: 如何选择锁类型?

  1. 优先尝试非公平锁
  2. 当出现明显饥饿问题时改用公平锁
  3. 对顺序有严格要求时使用公平锁

总结

理解公平锁与非公平锁的关键在于: 1. 公平锁通过hasQueuedPredecessors()保证顺序 2. 非公平锁通过”插队”机制提高吞吐 3. 选择时需要权衡公平性和性能 “`

推荐阅读:
  1. Java中怎么利用多线程实现并发编程
  2. java中各种锁的示例分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:互联网中如何制作一个成功的营销型网站

下一篇:Centos下rpm包怎么制作

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》