java - 这个spring代码线程安全吗?

标签 java multithreading spring hibernate

此类设计为在 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");
}

抱歉,信太多了,但我试图让您了解如何分析代码并选择正确的决策。因此,逐步得出结论:

  1. 尝试使 AdminDataRepository 对象线程安全并调用它 没有同步
  2. 如果不可能,请使用对象 adRep 作为 锁定
  3. 使用AdminData对象而不使用 volatile ,但它也是 值得回顾其内部代码

    附注此信息对于纯 Java 有效。我不能 100% 确定 Spring 没有一些内部逻辑来使对 bean 线程的调用安全。

关于java - 这个spring代码线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33646813/

相关文章:

java - 如何在 Java 和 JavaScript 之间传递字节数组

java - 与 Java 线程的双向通信

Spring Build ContextLoads 失败

java - Java 1.4 中的 String.format 等价物

java - Java中线程安全队列和 "master/worker"程序的模式/原则

java - 如何防止java中同步代码中的happens-before?

Python - 从线程池调用 Linux 命令不起作用

python - 未发现 djutils 队列命令

java - 为什么我的 Java Web 应用程序中出现 NoSuchMethodError?

spring - 集群中的文件夹轮询