如何为我的Java YAML-API实现线程安全


示例yaml文件:example.yml表示为DreamYaml对象。该文件在初始化时加载并解析。

现在,让我们假设多个线程同时加载、编辑和保存该文件。以便为同一文件创建多个DreamYaml对象。在这种情况下,实现线程安全的最佳方法是什么?

我的想法是在load()方法内部调用静态同步的lockFile()方法,在save()方法内部调用unlockFile()方法。这样做的问题是,每次都必须在最后调用save()才能解锁文件,而且这可能会为其他线程阻止文件的时间比实际需要的时间长得多。

我想最好的方法是在save()内部调用lockFile()方法,然后在锁定的部分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文件路径及其锁的hashmap。这意味着每个文件有一个ReentrantLock。现在,用户可以在加载yaml文件之前调用lockFile()以实现线程安全,并在加载完成后调用unlockFile()

但我不完全确定。。。你怎么看?

关于我的Java-YAML API的更多详细信息:https://github.com/Osiris-Team/Dream-Yaml

仅在保存时锁定数据不是线程安全的。

假设数据中的某个项目包含值0。两个线程同时希望将该值增加1。可能出现以下经典竞赛条件:

两个线程同时读取0。两者都计算出新的值为CCD_ 19。一个线程获取锁,加载当前数据,将项设置为1,保存。然后,其他线程获取锁,加载更新的数据,用1替换该项,因为新值已经计算完毕,然后保存。结果是文件中的项目是1,而它应该是2

无论您是将值保存到文件还是将其保存在内存中,为了实现线程安全,从读取值到提交更新值,都需要一个独占锁(或等效机制(。

实现线程安全的最佳方法是什么,这个问题通常无法回答。它取决于您的用例,可能需要对系统的行为进行广泛的分析。如前所述,从加载到保存的独占锁将起作用(当然不会检测到文件的外部修改(。

目前尚不清楚为什么要一直保存和加载数据,启动时加载一次文件并将其保存在内存中似乎要简单得多。当您进行修改时,您会保存文件,但会将其保存在内存中。只有当文件a(很大,而b(修改很少发生时,这才是糟糕的。