此类旨在在Spring Boot控制器中运行。管理数据存在于Oracle表中,并且只有一个记录。这是必需的,因为数据可以被另一个应用程序更改,如果是这样,这个应用程序需要读取新数据。
因此AdminData是一个实体bean(Hibernate)。实际上,管理数据几乎永远不会更新,但这是一个高容量的Web应用程序,因此可以非常频繁地读取数据。每次打电话给GET和POST都需要它。
我考虑过使用AtomicReference<>但在这种情况下,我不确定它是否比使用volatile关键字更好。
我认为这是线程安全的,因为:
1 - get()方法只返回一个引用,在Java中,获取或更新引用是原子的。
2 - 由于对存储库的调用,onDatabaseChangeNotification()调用可能不会以原子方式执行,但此方法只能通过Oracle的调用执行,因此只有一个线程可以运行它。同样,对cachedAd的引用分配应该是原子的。
3 - 我认为对setInitialValue()的调用可能只会被一个线程执行,但我不确定所以我添加了synchronized。
我是对的吗?谢谢你的帮助。
@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");
}
}
更新,基于评论和一些睡眠:
如果AdminData不是线程安全的并且无法使线程安全(通过使其成为不可变的),也许这种方法可行,尽管我关注性能:
public AdminData get()
{
AdminData tmp = cachedAd;
return tmp.clone();
}
另一个更新
基于更多评论和更多研究,我改写了课程。
我决定我需要的是一个不可变对象来保存管理数据,因此我创建了一个名为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。
答案 0 :(得分:0)
我认为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
对象)作为锁定对象,并且只有在AdminDataRepository adRep
中注入AdminDataCacher
时它才会安全。但是如果同一个singelton对象AdminDataRepository adRep
将被注入某个类 - 你遇到麻烦,因为synchronized
没用,你仍然可以同时接到几个adRep.findByKey()
的调用(一个来自AdminDataCacher
,还有一些来自注入了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
对象线程安全并调用它
没有synchronized
adRep
锁使用AdminData
对象而不是volatile
,但也是如此
值得查看其内部代码
P.S。此信息在普通Java的情况下有效。我并不是100%确定Spring没有一些内部逻辑来调用bean线程安全。
答案 1 :(得分:0)
2 - 由于对存储库的调用,onDatabaseChangeNotification()调用可能不会以原子方式执行,但此方法只能通过Oracle的调用执行,因此只有一个线程可以运行它。
这不是思考线程安全的正确方法。
方法永远不需要同步。无论有多少线程同时在程序中调用相同的方法,方法都不会发生任何坏事。
需要同步的数据。
您的onDatabaseChangeNotification(...)
方法调用adRep.findByKey(1L)
。您需要考虑的是,其他线程可以同时访问或修改任何相同的程序状态。 那是你可能遇到麻烦的地方。