我使用Java已经一个月了,编程方面还算是一个业余爱好者,所以如果我有错误的地方,请随时纠正。也许我会提供一些多余的细节,但我现在感到非常困惑,无法确定什么才是重要的。
因此,我一直在开发多线程客户端-服务器应用程序。所有线程都使用同一个对象,在这个对象中存储某些配置值和共享记录器;该对象在服务器线程中初始化,然后作为参数传递给客户端线程类构造函数。最初假设该对象的字段仅在服务器启动时更改一次,因此并没有担心并发访问,但现在需要在修改配置文件时重新读取某些配置值,而无需重新启动服务器。
在进行一些研究后,浮现出的第一个想法是创建一个同步方法,在请求该类的某些值时调用它,并且如果我们的配置文件自上次访问以来发生了更改,则重新读取这些值并立即返回,否则直接返回,如下所示:
十分钟后,我了解到这被称为双重检查锁定。再过十分钟,当我阅读这篇文章时,我的世界两次崩溃:第一次是因为我了解到它据说由于内部CPU缓存而不能工作,第二次是当我阅读了关于对长整型和浮点类型的操作不具有原子性的内容时。或者它是否仍然有效,因为没有涉及对象创建?而且,由于长整型的操作是非原子的,将“lastModified”声明为易失性变量是否真的足够?如果可能的话,我更希望能够得到一个关于它是否有效的适当解释。提前致谢。
因此,我一直在开发多线程客户端-服务器应用程序。所有线程都使用同一个对象,在这个对象中存储某些配置值和共享记录器;该对象在服务器线程中初始化,然后作为参数传递给客户端线程类构造函数。最初假设该对象的字段仅在服务器启动时更改一次,因此并没有担心并发访问,但现在需要在修改配置文件时重新读取某些配置值,而无需重新启动服务器。
在进行一些研究后,浮现出的第一个想法是创建一个同步方法,在请求该类的某些值时调用它,并且如果我们的配置文件自上次访问以来发生了更改,则重新读取这些值并立即返回,否则直接返回,如下所示:
<This code is inside "config" class, instance of which is shared between threads>
private static long lastModified;
private static File configFile;
public class ChangingVariableSet
{
<changing variables go here>
}
private synchronized void ReReadConfig
{
long tempLastMod = configFile.lastModified();
if(lastModified == tempLastMod)
return;
<reread values here>
lastModified = tempLastMod;
}
public ChangingVariableSet GetValues()
{
ReReadConfig();
<return necessary values>
}
(上述代码未经测试,我只是想传达一般的想法)。
但我并不喜欢每次请求值时都阻止它,因为这似乎很昂贵,而且我的应用程序有可能在将来成为一个具有很多线程的高负载系统。所以我有一个“好”思路-在锁定之前检查文件是否已被修改,然后再次在锁定方法内部,尽可能避免锁定:
public ChangingVariableSet GetValues()
{
if(lastModified == configFile.lastModified())
ReReadConfig();
<return necessary values>
}
十分钟后,我了解到这被称为双重检查锁定。再过十分钟,当我阅读这篇文章时,我的世界两次崩溃:第一次是因为我了解到它据说由于内部CPU缓存而不能工作,第二次是当我阅读了关于对长整型和浮点类型的操作不具有原子性的内容时。或者它是否仍然有效,因为没有涉及对象创建?而且,由于长整型的操作是非原子的,将“lastModified”声明为易失性变量是否真的足够?如果可能的话,我更希望能够得到一个关于它是否有效的适当解释。提前致谢。
P.S: 我知道类似的问题已经被解答了几次了,也许停止吹毛求疵并同步整个“getValue”方法而不是“ReReadConfig”会更好,但我正在努力学习更多关于线程安全编程的知识,并避免在未来遇到类似的问题。我还为任何可能存在的语法和拼写错误道歉,我不太懂英语。
编辑: 首先,我修正了最后一个“if”子句中的一个拼写错误。其次 - 警告,上面的代码不是线程安全的,请勿使用!在方法中。
public ChangingVariableSet GetValues()
{
if(lastModified == configFile.lastModified())
ReReadConfig();
<return necessary values>
}
如果在if检查和返回值之间的时间段内更新了文件,则线程B可能会在线程A开始返回值之前启动ReReadConfig,导致必要数据部分更改的危险。似乎正确的做法是使用ReentrantReadWriteLock避免过度阻塞,但我仍然想使用双重检查来避免过多(且昂贵,文件被认为是大型XML)的配置重新读取:
<...>
private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final Lock read = readWriteLock.readLock();
private static final Lock write = readWriteLock.writeLock();
private void ReReadConfig
{
write.lock();
long tempLastMod = configFile.lastModified();
if(lastModified == tempLastMod)
return;
<reread values here>
lastModified = tempLastMod;
write.release();
}
public ChangingVariableSet GetValues()
{
if(lastModified == configFile.lastModified())
ReReadConfig();
read.lock();
<get necessary values>
read.release();
<return necessary values>
}
现在它至少看起来是线程安全的,但是问题仍然存在,当检查时依赖于volatile的“lastModified”变量:我曾经在某个地方读到过,volatile变量无法保证非原子操作上的任何东西,并且“long”类型的读/写是非原子的。