Java模拟ReentrantLock实现自己的显示锁BooleanLock

本文通过模拟ReentrantLock,创建自定义的显示锁MyLock,实现了线程同步、限时锁获取、响应中断等功能,深入理解ReentrantLock特性。

一、前言

Java通过synchronized关键字来为我们提供线程安全的保证,大多数情况下使用synchronized是没有问题的,然而synchronized有自身的缺陷。例如:当其它线程持有锁时,当前请求获取锁的线程必须等待。等待的时长是无法控制的,而且等待过程中无法响应中断。

正是为了解决synchronized这些的缺陷,Java提供了一个显示锁ReentrantLock来实现线程安全,在满足重入性,独占性的条件下,为我们的加锁,解锁和锁获取提供了灵活的操作。

ReentrantLock的常用方法如下:

lockInterruptibly():提供响应中断的锁获取操作。

tryLock(long timeout, TimeUtil unit):指定时间内响应中断和指定时间内的锁获取操作。

tryLock():以非阻塞的方法尝试获取锁,无论是否获取锁都立刻返回,当成功获取锁返回true,否则返回false。

new ReentrantLock(boolean):构造方法可指定锁获取的公平性,即先申请获得锁的线程将先获得锁。synchronized是非公平的。

lock():以阻塞的形式获取锁,和使用synchronized一样的效果。

unlock():解锁。

isHeldByCurrentThread():持有锁的线程是否为当前线程。

本文将以ReentrantLock的这些特性为目标,通过synchronized+wait+notify来模拟一个自己的显示锁。代码灵感来源于 汪文君 老师的一书《高并发编程详解:多线程与架构设计》。

二、代码部分

1.定义自己的显示锁,它是一个接口,满足的功能参照备注:

import java.util.List;
import java.util.concurrent.TimeoutException;

public interface MyLock {

    /**
     * 实现可中断的锁获取操作
     *
     * @throws InterruptedException
     */
    void lock() throws InterruptedException;

    /**
     * 实现限时的锁获取操作
     *
     * @param timeMillis
     * @throws InterruptedException
     * @throws TimeoutException
     */
    void lock(long timeMillis) throws InterruptedException, TimeoutException;

    /**
     * 解锁
     */
    void unLock();

    /**
     * 返回等待获取锁的线程集合
     *
     * @return
     */
    List<Thread> getBlockedThreads();

    /**
     * 尝试获取锁,获取成功立刻返回true,否则返回false
     *
     * @return
     */
    boolean tryLock();

    /**
     * 判断当前线程是否持有锁
     *
     * @return
     */
    boolean isHeldByCurrentThread();
}

MyLock接口模拟了ReentrantLock的部分方法:MyLock的lock()方法模拟ReentrantLock的lockInterruptibly(),MyLock的lock(long timeMillis)方法模拟ReentrantLock的tryLock(long timeout, TimeUtil unit),MyLock的其余方法模拟ReentrantLock的同名方法。

2.定义BooleanLock,作为MyLock的实现类,代码如下:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;

public class BooleanLock implements MyLock {

    private boolean locked;
    private Thread currentThread; //当前持有锁的线程
    private final List<Thread> blockedThreads = new ArrayList<>(); //等待获取锁的线程

    @Override
    public void lock() throws InterruptedException {
        synchronized (this) {
            while (locked) {
                if (!blockedThreads.contains(Thread.currentThread())) {
                    blockedThreads.add(Thread.currentThread());
                }
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    blockedThreads.remove(Thread.currentThread());
                    throw e;
                }
            }
            locked = true;
            currentThread = Thread.currentThread();
            blockedThreads.remove(currentThread);
        }
    }

    @Override
    public void lock(long timeMillis) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (timeMillis <= 0) {
                this.lock();
            } else {
                long remainMills = timeMillis;
                long endTime = System.currentTimeMillis() + remainMills; //截至时刻
                while (locked) {
                    if (remainMills <= 0) { //剩余等待时间
                        throw new TimeoutException("cannot get lock during " + remainMills);
                    }
                    if (!blockedThreads.contains(Thread.currentThread())) {
                        blockedThreads.add(Thread.currentThread());
                    }
                    this.wait(remainMills); //等待一定时间
                    remainMills = endTime - System.currentTimeMillis(); //重新计算等待时间:当前时间点与while执行之前的时间点之差。
                }
                locked = true;
                currentThread = Thread.currentThread();
                blockedThreads.remove(currentThread);
            }
        }
    }

    @Override
    public void unLock() {
        synchronized (this) {
            if (currentThread == Thread.currentThread()) {
                locked = false;
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedThreads);
    }

    @Override
    public boolean tryLock() {
        synchronized (this) {
            if (!locked) {
                locked = true;
                currentThread = Thread.currentThread();
                blockedThreads.remove(currentThread);
                return true;
            }
            return false;
        }
    }

    @Override
    public boolean isHeldByCurrentThread() {
        synchronized (this) {
            if (Thread.currentThread() == currentThread) {
                return true;
            }
            return false;
        }
    }
}

三.测试代码

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class TestMyLock {

    private Random random = new Random();
    private MyLock myLock = new BooleanLock();

    public void syncMethod() {
        try {
            myLock.lock();
            int rand = random.nextInt(10);
            System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
            TimeUnit.SECONDS.sleep(rand);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }

    public void syncTryMethod() {
        try {
            if (myLock.tryLock()) {
                int rand = random.nextInt(10);
                System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
                TimeUnit.SECONDS.sleep(rand);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }

    public void syncMethodOut() {
        try {
            myLock.lock(1000);
            int rand = random.nextInt(10);
            System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
            TimeUnit.SECONDS.sleep(rand);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }


    static void testSync() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncMethod();
            }, i + "");
            thread.start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testMyLock.myLock.getBlockedThreads());
    }

    static void testTrySync() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncTryMethod();
            }, i + "");
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testMyLock.myLock.getBlockedThreads());
    }

    static void testInterrupt() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncMethod();
            }, i + "");
            thread.start();
            if (i % 2 == 0) {
                thread.interrupt();
            }
        }
    }

    static void testTimeOut() throws InterruptedException {
        TestMyLock blt = new TestMyLock();
        new Thread(blt::syncMethod, "Tl").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(blt::syncMethodOut, "T2");
        t2.start();

    }

    public static void main(String[] args) throws InterruptedException {
        testSync();
//        testTrySync();
//        testInterrupt();
//        testTimeOut();
    }
}

1.运行testSync(),看到控制台有条不紊的打印出加锁和解锁的操作,说明MyLock实现了线程同步的功能。

0 hold lock, and will sleep 5 seconds
[Thread[1,5,main], Thread[2,5,main], Thread[5,5,main], Thread[4,5,main], Thread[3,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
0 release lock
9 hold lock, and will sleep 3 seconds
9 release lock
1 hold lock, and will sleep 3 seconds
1 release lock
8 hold lock, and will sleep 8 seconds
8 release lock
2 hold lock, and will sleep 6 seconds
2 release lock
7 hold lock, and will sleep 6 seconds
7 release lock
5 hold lock, and will sleep 8 seconds
5 release lock
6 hold lock, and will sleep 5 seconds
6 release lock
4 hold lock, and will sleep 3 seconds
4 release lock
3 hold lock, and will sleep 7 seconds
3 release lock

2.运行testTrySync(),控制台打印如下,可以看出使用tryLock时,若线程0持有锁,其它线程无法获取锁,故等待获取锁的集合为空集合。

0 hold lock, and will sleep 4 seconds
[]
0 release lock

3.运行testInterrupt(),控制台打印如下

java.lang.InterruptedException
0 hold lock, and will sleep 8 seconds
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
0 release lock
	at BooleanLock.lock(BooleanLock.java:20)
9 hold lock, and will sleep 4 seconds
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at TestMyLock.syncMethod(TestMyLock.java:15)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
9 release lock
1 hold lock, and will sleep 1 seconds
1 release lock
7 hold lock, and will sleep 0 seconds
7 release lock
3 hold lock, and will sleep 7 seconds
3 release lock
5 hold lock, and will sleep 6 seconds
5 release lock

这里的打印有点乱,要注意e.printStackTrace方法本质上是调用System.err对象的println方法,而System.out.println方法属于System.out对象,故它们是属于不同的对象,也就是线程异步的!可以看出0,2,4,6线程都响应了中断!

4.运行testTimeOut(),控制台打印如,可以看出当线程T1已经获取锁,并且睡眠3s,线程T2尝试在1s内获取锁,结果失败。

Tl hold lock, and will sleep 3 seconds
java.util.concurrent.TimeoutException: cannot get lock during 0
	at BooleanLock.lock(BooleanLock.java:42)
	at TestMyLock.syncMethodOut(TestMyLock.java:47)
	at java.lang.Thread.run(Thread.java:745)
Tl release lock

四、小结

本文演示的案例仅仅是为了更好的理解显示锁特有的性质,ReentrantLock自身的实现运用了大量的AQS和CAS操作,其细节和原理远比本文复杂的多。实际项目开发我们直接使用ReentrantLock即可,不必大费周章定义自己的显示锁。

 

<think>我们正在讨论JavaReentrantLock的公平机制。根据引用[3],ReentrantLock的构造函数可以传入一个布尔值fair,当fair为true时,创建的是公平(FairSync),否则是非公平(NonfairSync)。默认情况下,ReentrantLock使用的是非公平。 公平的工作原理: 公平的核心思想是:多个线程按照申请的顺序来获取,即先到先得。在公平的机制下,当被释放时,等待时间最长的线程(即等待队列中的第一个线程)会获得。 非公平则不同,当被释放时,所有等待的线程都有机会竞争,而不管等待时间的长短。这样可能导致某些线程一直获取不到(饥饿),但非公平的吞吐量通常更高。 引用[1]提到:在等待的过程中,如果有任意新的线程妄图获取,都是有很大的几率直接获取到的(这是针对非公平的情况)。而公平则不会,公平会严格按照等待队列的顺序来分配。 公平实现原理(基于AQS): ReentrantLock的公平和非公平都是通过继承AbstractQueuedSynchronizer(AQS)来实现的。AQS内部维护了一个双向链表(CLH队列)来管理等待的线程。 公平(FairSync)在尝试获取时(lock方法)会调用acquire(1)方法,而acquire方法会先调用tryAcquire方法尝试获取。在tryAcquire方法中,公平会先检查是否有其他线程在等待(即等待队列中是否有前驱节点),如果有,则当前线程不会去竞争,而是乖乖地加入队列末尾等待。这样保证了获取的顺序与等待顺序一致。 具体代码逻辑(简化): 1. 当线程尝试获取(调用lock()方法)时,会调用Sync的lock方法。对于公平,lock方法最终调用的是FairSync的tryAcquire方法。 2. 在FairSync.tryAcquire()中: - 首先检查当前的状态(state变量),如果state为0,表示没有被任何线程持有。 - 然后,它会检查等待队列中是否有其他线程在等待(hasQueuedPredecessors()方法)。如果没有,则尝试使用CAS设置state为1,并将当前线程设置为的持有者。 - 如果有其他线程在等待,那么当前线程不会尝试获取,而是直接返回false,然后进入队列等待。 - 如果state不为0,且当前线程已经是的持有者,那么可以重入(state+1)。 3. 如果tryAcquire失败,则会将当前线程加入等待队列,并可能进入阻塞状态。 非公平(NonfairSync)在获取时不会检查等待队列,而是直接尝试获取(通过CAS修改state),如果获取失败,再像公平一样加入队列。 使用方法: 根据引用[2]给出的使用案例,我们可以这样使用公平: ```java class X { // 创建公平:传入true private final ReentrantLock lock = new ReentrantLock(true); public void m() { lock.lock(); // 获取(如果被其他线程占用,则阻塞直到获取) try { // ... 业务代码 } finally { lock.unlock(); // 释放 } } } ``` 注意: 1. 使用公平可能会降低程序的吞吐量,因为每次获取都要检查队列,而且唤醒等待线程需要上下文切换。 2. 公平可以防止线程饥饿,适用于对线程公平性要求较高的场景。 引用[1]还提到:ReentrantLock都不会使得线程中断,除非开发者自己设置了中断位。也就是说,在等待的过程中,线程不会因为中断而自动退出等待,但我们可以使用lockInterruptibly()方法来响应中断。 总结: - 公平:先来先得,严格按照等待队列的顺序分配。 - 非公平:允许插队,可能造成线程饥饿,但性能较高。 根据实际需求选择公平还是非公平。在大多数情况下,非公平的性能更好,因为减少了线程切换的开销。但是,在需要保证公平性的场景下,应使用公平。 相关问题: 1. 在什么场景下使用公平比非公平更合适? 2. 公平的等待队列是如何实现的? 3. 如何避免使用ReentrantLock时发生死?</think>### Java `ReentrantLock` 公平机制详解 #### 一、公平工作原理 `ReentrantLock` 的公平通过 **CLH队列** 实现,核心原则是 **先请求先获取**(FIFO)。当多个线程竞争时: 1. **队列管理** 所有等待线程被存入 **双向链表队列**,新请求线程自动加入队尾 ```java // CLH队列节点结构(简化) static final class Node { volatile Thread thread; // 等待线程 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 } ``` 2. **获取逻辑** - 当释放时,**只唤醒队首线程**(等待时间最长) - 新线程尝试获取时,会检查队列是否为空: ```java protected final boolean tryAcquire(int acquires) { if (!hasQueuedPredecessors()) { // 检查是否有前驱节点 // 无等待线程时才尝试获取 } return false; } ``` - 若有线程在队列中等待,新线程直接入队(不参与竞争)[^1][^3] 3. **避免线程饥饿** 公平通过强制排队机制确保每个线程最终都能获取资源,解决非公平可能导致的线程饥饿问题[^1]。 --- #### 二、公平使用方式 ##### 1. 创建公平 ```java // 构造函数传入true创建公平 ReentrantLock fairLock = new ReentrantLock(true); ``` ##### 2. 标准使用模板 ```java public class FairLockExample { private final ReentrantLock lock = new ReentrantLock(true); // 公平 public void criticalSection() { lock.lock(); // 阻塞直到获取 try { // 临界区代码(保证线程安全) System.out.println(Thread.currentThread().getName() + " 执行"); Thread.sleep(100); // 模拟处理 } finally { lock.unlock(); // 必须释放 } } } ``` ##### 3. 带超时的公平 ```java public boolean tryCriticalSection(long timeout, TimeUnit unit) throws InterruptedException { if (lock.tryLock(timeout, unit)) { // 限时等待 try { // 临界区操作 return true; } finally { lock.unlock(); } } return false; // 超时未获取 } ``` --- #### 三、公平 vs 非公平性能对比 | **指标** | 公平 | 非公平 | |----------------|--------------------------------|--------------------------------| | **吞吐量** | 较低(线程切换开销大) | 较高(减少线程切换)[^1] | | **响应时间** | 稳定(避免长等待) | 波动(可能饥饿) | | **适用场景** | 资源分配敏感场景 | 高并发短任务场景 | | **队列检查** | 每次获取都检查队列 | 直接尝试CAS获取[^3] | > 💡 **最佳实践**:对线程公平性要求高(如交易系统)选公平;追求吞吐量(如Web服务器)选非公平。 --- #### 四、典型应用场景 1. **资源配额系统** 确保每个用户请求按到达顺序处理 ```java public void allocateResource(User user) { fairLock.lock(); try { if (quotaManager.hasQuota(user)) { quotaManager.allocate(user); } } finally { fairLock.unlock(); } } ``` 2. **任务调度器** 保证任务执行顺序与提交顺序一致 ```java public void submitTask(Runnable task) { taskQueue.add(task); fairLock.lock(); try { executor.execute(taskQueue.poll()); } finally { fairLock.unlock(); } } ``` --- #### 五、注意事项 1. **避免嵌套** 公平嵌套调用可能导致死: ```java lock.lock(); // 第一次获取 lock.lock(); // 第二次获取(需等待队列中其他线程) ``` 解决方案:使用`getHoldCount()`检查重入次数 ```java if (lock.getHoldCount() == 0) { lock.lock(); } ``` 2. **监控等待队列** 通过`getQueueLength()`实现预警: ```java if (lock.getQueueLength() > WARNING_THRESHOLD) { alertSystem.notify("等待队列过长!"); } ``` 3. **死预防** 结合`tryLock`设置不同超时时间: ```java if (lockA.tryLock(100, MILLISECONDS)) { try { if (lockB.tryLock(300, MILLISECONDS)) { // ... } } finally { lockA.unlock(); } } ``` > ⚠️ 公平性能比非公平低约**10倍**(实测数据),仅在必要时使用[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alphathur

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值