这个春天代码线程安全吗?

时间:2015-11-11 08:36:59

标签: java multithreading spring hibernate

此类旨在在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。

2 个答案:

答案 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");
}

很抱歉信太多了,但我试着告诉你如何分析代码并选择正确的决定。所以,一步一步地结论:

  1. 尝试使AdminDataRepository对象线程安全并调用它 没有synchronized
  2. 如果无法使用对象adRep
  3. 使用AdminData对象而不是volatile,但也是如此 值得查看其内部代码

    P.S。此信息在普通Java的情况下有效。我并不是100%确定Spring没有一些内部逻辑来调用bean线程安全。

答案 1 :(得分:0)

  

2 - 由于对存储库的调用,onDatabaseChangeNotification()调用可能不会以原子方式执行,但此方法只能通过Oracle的调用执行,因此只有一个线程可以运行它。

这不是思考线程安全的正确方法。

方法永远不需要同步。无论有多少线程同时在程序中调用相同的方法,方法都不会发生任何坏事。

需要同步的数据

您的onDatabaseChangeNotification(...)方法调用adRep.findByKey(1L)。您需要考虑的是,其他线程可以同时访问或修改任何相同的程序状态。 是你可能遇到麻烦的地方。