如何为我的java yaml api实现线程安全?

lmvvr0a8  于 2021-08-20  发布在  Java
关注(0)|答案(1)|浏览(305)

yaml文件示例: example.yml 表示为 DreamYaml 对象在初始化时加载并解析该文件。
现在让我们设想多个线程同时加载、编辑和保存该文件。所以多 DreamYaml 为同一文件创建对象。在这种情况下,实现线程安全的最佳方法是什么?
我的想法是调用静态同步 lockFile() 方法内部的 load() 方法与 unlockFile() 内部 save() 方法。问题是,你必须打电话 save() 每次结束时,文件都会被解锁,这也可能会导致其他线程阻塞文件的时间比实际需要的时间长得多。
我想最好的办法是打电话给 lockFile() 方法内部 save() ,然后在锁定的区域内, load() 再次删除该文件,以便更新内存中的当前对象值,并与实际/真实的当前值匹配。然后简单地 save() 打开文件并释放锁。在这种情况下,我必须确保最近对值进行的内存更改不会从 load() 呼叫我不认为这种方法有任何更大的缺点*编辑:我刚刚发现这种方法的主要缺点是它实际上不是线程安全的。幸亏https://stackoverflow.com/a/68313798/13600212

**编辑:这是我想到的:

/**
     * If you access the same yaml file from multiple threads, its recommended to lock the file before loading it. <br>
     * Remember to {@link #unlockFile()} so that other threads can work with the file too. <br>
     * If you don't do that, other threads will stay stuck at {@link #lockFile()} forever. <br>
     * Example: <br>
     * <pre>
     *     DreamYaml yaml = new DreamYaml("example.yml");
     *     yaml.lockFile();
     *     yaml.load();
     *     // Do changes to file here
     *     yaml.save();
     *     yaml.unlockFile();
     * </pre>
     */
    public synchronized void lockFile(){
        if (file!=null){
            ReentrantLock lock = null;
            synchronized (pathsAndLocks){
                if (pathsAndLocks.containsKey(file.getAbsolutePath()))
                    lock = pathsAndLocks.get(file.getAbsolutePath()); // If another thread has already the locked, the current thread will wait until it gets unlocked
                else{
                    lock = new ReentrantLock();
                    pathsAndLocks.put(file.getAbsolutePath(), lock);
                }
            }
            lock.lock();
        }
    }

    /**
     * If you access the same yaml file from multiple threads, its recommended to lock the file before loading it. <br>
     * Remember to {@link #unlockFile()} so that other threads can work with the file too. <br>
     * If you don't do that, other threads will stay stuck at {@link #lockFile()} forever. <br>
     * Example: <br>
     * <pre>
     *     DreamYaml yaml = new DreamYaml("example.yml");
     *     yaml.lockFile();
     *     yaml.load();
     *     // Do changes to file here
     *     yaml.save();
     *     yaml.unlockFile();
     * </pre>
     */
    public synchronized void unlockFile(){
        if (file!=null){
            ReentrantLock lock = null;
            synchronized (pathsAndLocks){
                if (pathsAndLocks.containsKey(file.getAbsolutePath())){
                    lock = pathsAndLocks.get(file.getAbsolutePath()); // If another thread has already the locked, the current thread will wait until it gets unlocked
                    lock.unlock();
                    if(!lock.hasQueuedThreads())
                        pathsAndLocks.remove(file.getAbsolutePath());
                }
            }
        }
    }

在上面的代码中 pathsAndLocks 变量是包含yaml文件路径及其锁的哈希Map。这意味着每个文件有一个reentrantlock。现在用户可以打电话了 lockFile() 在加载yaml文件之前,实现线程安全和 unlockFile() 当它们完成时。
但我不完全确定。。。你怎么看?
有关我的java yaml api的更多详细信息:https://github.com/osiris-team/dream-yaml

vohkndzv

vohkndzv1#

仅在保存时锁定数据不是线程安全的。
假设数据中的某个项包含该值 0 . 两个线程同时希望将该值增加1。可能会出现以下经典争用条件:
两个线程都读入 0 同时这两种方法都会计算要使用的新值 1 . 一个线程获取锁,加载当前数据,将项设置为 1 ,保存。然后,另一个线程获取锁,加载更新的数据,用 1 因为新值已经计算过,所以保存。结果是文件中的项是 1 应该是什么时候 2 .
无论是将值保存到文件还是将其保留在内存中,为了实现线程安全,从读取值到提交更新的值,都需要一个独占锁(或等效机制)。
实现线程安全性的最佳方法是什么,这一问题通常无法回答。它取决于您的用例,可能需要对系统的行为进行广泛的分析。如前所述,从加载到保存的独占锁将起作用(当然不会检测文件的外部修改)。
不清楚为什么要一直保存和加载数据,在启动时加载一次文件并将其保存在内存中似乎要简单得多。进行修改时,保存文件,但将其保留在内存中。只有当文件a)很大,b)修改很少时,这才是不好的。

相关问题