You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于输入参数实现可变数量锁的Java两阶段死锁预防算法问询

嘿,这个问题我刚好有经验,咱们一步步来解决,顺便聊聊两阶段锁的优化思路~

一、核心需求实现:可变数量锁的尝试与释放

首先,我们需要解决两个关键问题:从输入文件解析进程和资源请求,以及实现可变数量锁的尝试-回滚-重试逻辑。

1. 调整Resource类的可访问性

你的Resource类里的ReentrantLock是私有变量,线程无法直接调用tryLock,所以我们需要添加一个getter方法(符合封装原则):

class Resource {
    private int resourceId;
    private ReentrantLock theLock;

    public Resource(int resourceId) {
        this.resourceId = resourceId;
        this.theLock = new ReentrantLock();
    }

    // 提供锁的访问入口
    public ReentrantLock getLock() {
        return theLock;
    }

    // 可选:封装资源使用逻辑,避免手动处理锁遗漏
    public void use(Runnable action) {
        theLock.lock();
        try {
            action.run();
        } finally {
            theLock.unlock();
        }
    }
}

2. 实现进程线程的锁尝试逻辑

每个进程对应一个线程,核心逻辑是:循环尝试获取所有需要的锁,中途失败则释放已持有的锁并重试;成功获取后执行业务逻辑,最后逆序释放所有锁。

class ProcessThread extends Thread {
    private List<Integer> sortedResourceIds; // 排序后的资源ID,优化冲突
    private Map<Integer, Resource> resourceMap;

    public ProcessThread(List<Integer> requestedResourceIds, Map<Integer, Resource> resourceMap) {
        // 关键优化:对资源ID排序,所有进程按相同顺序获取锁,打破循环等待条件
        this.sortedResourceIds = requestedResourceIds.stream()
                .sorted()
                .collect(Collectors.toList());
        this.resourceMap = resourceMap;
    }

    @Override
    public void run() {
        List<Resource> acquiredResources = new ArrayList<>();
        boolean allLocksAcquired = false;

        while (!allLocksAcquired && !Thread.currentThread().isInterrupted()) {
            acquiredResources.clear();
            allLocksAcquired = true;

            // 第一阶段:尝试获取所有锁
            for (Integer id : sortedResourceIds) {
                Resource res = resourceMap.get(id);
                // 无参tryLock立即返回,也可以加超时:tryLock(100, TimeUnit.MILLISECONDS)
                if (res.getLock().tryLock()) {
                    acquiredResources.add(res);
                } else {
                    allLocksAcquired = false;
                    break;
                }
            }

            if (!allLocksAcquired) {
                // 释放已获取的锁,逆序释放更规范(顺序其实不影响,但养成好习惯)
                for (int i = acquiredResources.size() - 1; i >= 0; i--) {
                    acquiredResources.get(i).getLock().unlock();
                }
                // 随机等待避免忙等,减少CPU占用
                try {
                    Thread.sleep(100 + new Random().nextInt(200));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
                System.out.printf("线程%d获取锁失败,释放已持有的锁,稍后重试%n", Thread.currentThread().getId());
            }
        }

        if (Thread.currentThread().isInterrupted()) {
            return;
        }

        try {
            // 第二阶段:执行业务逻辑(使用资源)
            System.out.printf("线程%d成功获取所有锁,开始执行操作%n", Thread.currentThread().getId());
            // 模拟资源使用时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放所有锁,逆序释放
            for (int i = acquiredResources.size() - 1; i >= 0; i--) {
                acquiredResources.get(i).getLock().unlock();
            }
            System.out.printf("线程%d释放所有锁,操作完成%n", Thread.currentThread().getId());
        }
    }
}

3. 解析输入文件

按照你指定的格式读取文件,解析出进程数、资源数和每个进程的资源请求列表:

public class TwoPhaseLockDemo {
    public static void main(String[] args) throws IOException {
        // 读取输入文件,按行解析更符合你的格式要求
        List<String> lines = Files.readAllLines(Paths.get("resource_requests.txt"));
        int processCount = Integer.parseInt(lines.get(0).trim());
        int resourceCount = Integer.parseInt(lines.get(1).trim());

        // 初始化资源池:资源ID从1到resourceCount
        Map<Integer, Resource> resourceMap = new HashMap<>();
        for (int i = 1; i <= resourceCount; i++) {
            resourceMap.put(i, new Resource(i));
        }

        // 解析每个进程的资源请求
        List<List<Integer>> processRequests = new ArrayList<>();
        for (int i = 2; i < 2 + processCount; i++) {
            String line = lines.get(i).trim();
            if (line.isEmpty()) {
                processRequests.add(new ArrayList<>());
                continue;
            }
            List<Integer> reqs = Arrays.stream(line.split("\\s+"))
                    .map(Integer::parseInt)
                    .collect(Collectors.toList());
            processRequests.add(reqs);
        }

        // 启动所有进程线程
        List<Thread> threads = new ArrayList<>();
        for (List<Integer> reqs : processRequests) {
            Thread thread = new ProcessThread(reqs, resourceMap);
            threads.add(thread);
            thread.start();
        }

        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
二、更优实现思路

上面的代码已经满足你的需求,但还有几个可以优化的点:

1. 锁排序优化(必做)

我在代码里已经加入了资源ID排序的逻辑,这是两阶段锁预防死锁的关键优化:所有进程按相同的顺序请求资源,打破死锁的「循环等待」必要条件,能大幅减少重试次数和冲突概率。

2. 避免忙等的进阶方案

当前用Thread.sleep是简单有效的方式,但高并发场景下可以用Condition来实现精准通知:当某个锁被释放时,通知等待该锁的线程重试。不过这会增加复杂度,需要为每个资源维护等待队列,适合对性能要求极高的场景。

3. 超时与中断机制

在tryLock时添加超时时间(比如tryLock(500, TimeUnit.MILLISECONDS)),避免线程无限期等待;同时处理中断信号,保证线程能优雅退出。

4. 资源使用的封装

把资源的业务逻辑封装到Resource类的use方法中,避免在线程里直接操作锁,降低出错概率(比如忘记释放锁)。

5. 监控与日志

添加日志记录线程的锁操作(获取、释放、重试),方便调试和排查问题。比如用SLF4J框架记录详细的操作日志。

内容的提问来源于stack exchange,提问作者Khatevate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:53:12