此类设计为在 Spring Boot Controller 中运行。管理数据位于 Oracle 表中,并且只有一条记录。这是必需的,因为数据可能会被另一个应用程序更改,如果更改,此应用程序需要读取新数据。
所以AdminData是一个实体bean(Hibernate)。实际上,管理数据几乎永远不会更新,但这是一个大容量的 Web 应用程序,因此数据读取非常频繁。每次调用 GET 和 POST 时都需要它。
我考虑过使用 AtomicReference<>,但在这种情况下,我不确定它是否比仅使用 volatile 关键字更好。
我认为这是线程安全的,因为:
1 - get() 方法仅返回一个引用,在 Java 中获取或更新引用是原子的。
2 - 由于对存储库的调用,onDatabaseChangeNotification() 调用可能不会以原子方式执行,但此方法只能通过来自 Oracle 的调用来执行,因此只有一个线程运行它。同样,对cachedAd 的引用分配应该是原子的。
3 - 我认为对 setInitialValue() 的调用可能也只会由一个线程执行,但我不确定,所以我添加了同步。
我说得对吗?感谢您的帮助。
@DependsOn("DecLogger")
@Service
public class AdminDataCacher implements DatabaseChangeListener
{
@Autowired
private AdminDataRepository adRep;
private volatile AdminData cachedAd = null;
public AdminData get()
{
return cachedAd;
}
@Override
public void onDatabaseChangeNotification(oracle.jdbc.dcn.DatabaseChangeEvent e)
{
cachedAd = adRep.findByKey(1L);
DecLogger.DEC_LOGIN.finer(() -> "Oracle DCN Call on Admin Data - Invalidating Cached Data");
}
@PostConstruct
private synchronized void setInitialValue()
{
cachedAd = adRep.findByKey(1L);
DecLogger.DEC_LOGIN.finer(() -> "AdminDataCacher - Initial value set");
}
}
更新,基于评论和一些 sleep :
如果 AdminData 不是线程安全的并且无法使其成为线程安全的(通过使其不可变),也许这种方法会起作用,尽管我担心性能:
public AdminData get()
{
AdminData tmp = cachedAd;
return tmp.clone();
}
另一个更新
根据更多的评论和更多的研究,我重写了类(class)。
我决定需要一个不可变的对象来保存管理数据,因此我创建了一个额外的不可变类,名为 AdminDataImmutable。由于该类是不可变的,因此本质上是线程安全的,因此我可以将其返回给每个调用者,从而避免克隆缓存实例的开销,而且我不必担心将来其他开发人员滥用它,而且我也不必捍卫/保护其中的副本。
正如所指出的,当数据库发生更改时,我应该在存储库上进行同步,并且我可以不用担心地更新缓存对象的引用,因为在 Java 中,引用更新在设计上是原子的。
现在,在 get() 方法中,我可以简单地返回引用。代码如下。这个新版本有意义吗???
谢谢!
@DependsOn("DecLogger")
@Service
public class AdminDataCacher implements DatabaseChangeListener
{
private volatile AdminDataImmutable cachedAd;
@Autowired
private AdminDataRepository adRep;
public AdminDataImmutable get()
{
return cachedAd;
}
@Override
public void onDatabaseChangeNotification(oracle.jdbc.dcn.DatabaseChangeEvent e)
{
DecLogger.DEC_LOGIN.finer(() -> "Oracle DCN Call on Admin Data - Invalidating Cached Data");
synchronized(adRep)
{
AdminDataEntity ade = adRep.findByKey(1L);
cachedAd = new AdminDataImmutable(ade);
}
}
@PostConstruct
private void loadInitialValue()
{
synchronized(adRep)
{
AdminDataEntity ade = adRep.findByKey(1L);
cachedAd = new AdminDataImmutable(ade);
}
}
}
最后更新
我使cachedAd变得不稳定。
最佳答案
我认为 volatile AdminData
在这里是无用的,因为这只能安全地更新引用,但不能使AdminData
对象本身线程安全。正如您提到的 get() 方法,在 java 中引用的更新始终是原子操作。所以你试图过度保护引用。如果您想确保 AdminData 对象本身是线程安全的,您应该查看 AdminData 对象的代码。
关于 2) 和 3) 我会注意到,可能值得查看一下 findByKey
方法的代码并使其线程安全,但不要尝试假设调用者线程上的数字(似乎您在这两种情况下都不确定)。尝试使线程安全代码的堆栈尽可能高 - 这将减少关键部分的数量并降低代码复杂性。
如果您无法返工或审查 AdminDataRepository 的代码,那么在 2) 的情况下,您假设只有一个调用者。但类比 3) 可能值得添加 synchronized
,因为 findByKey
仍然有可能被至少两个线程同时调用:至少一个来自非线程安全的 onDatabaseChangeNotification()
的调用(但可能有更多线程)和一个来自同步 setInitialValue()
的调用。因此,您可以保护一种方法,但仍然可以对 findByKey()
进行两次并发调用。如果 findByKey()
与 adRep 对象中的某些共享数据交互,而不仅仅是从 Oracle 数据库检索数据,则可能会导致问题(作为简单的示例,想象一下 findByKey
的每次调用都会增加一些内部计数器,该计数器在所有调用之间共享)。
接下来,仅将 synchronized
放在方法 onDatabaseChangeNotification()
上还有一个陷阱。在本例中,您使用 this
对象(AdminDataCacher
对象)作为锁定对象,并且如果仅在 AdminDataCacher
中注入(inject) AdminDataRepository adRep
,它将是安全的。但是,如果同一个单例对象 AdminDataRepository adRep
将被注入(inject)到某个类中 - 您就会遇到麻烦,因为 synchronized
没有用,您仍然可以同时多次调用 adRep.findByKey()
(一个来自 AdminDataCacher
,还有一些来自注入(inject) AdminDataRepository adRep
的其他类)。在这种情况下,您应该保护 adRep
对象:
@Override
public void onDatabaseChangeNotification(oracle.jdbc.dcn.DatabaseChangeEvent e)
{
synchronized(adRep) {
cachedAd = adRep.findByKey(1L);
}
DecLogger.DEC_LOGIN.finer(() -> "Oracle DCN Call on Admin Data - Invalidating Cached Data");
}
@PostConstruct
private void setInitialValue()
{
synchronized(adRep) {
cachedAd = adRep.findByKey(1L);
}
DecLogger.DEC_LOGIN.finer(() -> "AdminDataCacher - Initial value set");
}
抱歉,信太多了,但我试图让您了解如何分析代码并选择正确的决策。因此,逐步得出结论:
- 尝试使
AdminDataRepository
对象线程安全并调用它 没有同步
- 如果不可能,请使用对象
adRep
作为 锁定 使用
AdminData
对象而不使用volatile
,但它也是 值得回顾其内部代码附注此信息对于纯 Java 有效。我不能 100% 确定 Spring 没有一些内部逻辑来使对 bean 线程的调用安全。
关于java - 这个spring代码线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33646813/