【问题标题】:Volatile and Synchronized to Solve Race Condition: Singleton Member Field易失和同步以解决竞争条件:单例成员字段
【发布时间】:2021-05-12 16:04:10
【问题描述】:

我在尝试理解和修复 Fortify 扫描报告的错误时遇到了一些问题。我有这门课:

public class DaoImpl extends BaseDaoImpl {
    private static volatile String sNric;
    
    synchronized private void setInfo(InfoTO pers) {
        sNric = pers.getNRIC();
    }

    synchronized public InfoTO getInfo() { 
        InfoTO pers = new InfoTO();
        sNric = retrieveDetail();
        pers.setNRIC(sNric);
    }

    synchronized private String retrieveDetail() {
        // some logic to get info from database
    }
}

我的代码最初没有 static volatilesynchronized 关键字。 Fortify 在sNricsNric = retrieveDetail(); 的变量声明中报告了Race Condition: Singleton Member Field 警告

我去研究发现this solution。但是,我不太确定 volatile 和 synchronized 的概念。上面提出的解决方案会导致一些死锁问题吗?

【问题讨论】:

  • volatile 不会把你从种族中拯救出来,不是那个。您需要明确区分可见性和原子性,或两者兼而有之。
  • 为什么sNric 是静态的?鉴于它只能通过非静态方法访问,我认为该字段也应该是非静态的。如果有多个 DaoImpl 实例,则会发生争用情况,因为您的方法在一个实例上同步,而该字段是静态的。
  • @MarkRotteveel 更大的问题是,为什么这个变量存在?唯一的读取紧跟在写入之前,因此sNric = retrieveDetail(); pers.setNRIC(sNric); 可以替换为pers.setNRIC(retrieveDetail());,这表明setInfosNric 的写入根本没有效果,没有人会读取该值。跨度>
  • @MarkRotteveel 一个影响何时访问哪个变量的代码简化对于线程安全问题来说是不可行的。由于数据访问是线程安全(防止数据竞争)的全部意义所在……
  • @MarkRotteveel 我们不知道这种过度简化是否真的发生过。 OP 的真实代码也可能像这里显示的那样毫无意义。这不是一个密切的原因。

标签: java multithreading synchronized volatile


【解决方案1】:

volatilesynchronized 的“概念”是你可能不应该这样做。

如果您在所有访问和更新共享变量(例如您的sNric 变量)的方法中使用synchronized,那么声明volatile 是多余且低效的。

关于你关于死锁的问题,我看不出有任何方法可以让你仅仅根据上面的代码得到死锁。 但是,您没有向我们展示InfoTO 的代码或使用这些类的代码。涉及DaoImpl实例锁和其他锁的死锁并非不可能发生。

如果您担心getInfo 调用this.retrieveDetail 可能会出现死锁。这里只涉及一个(DaoImpl 实例)锁,Java 原始锁是可重入的。 (如果线程试图获取它已经持有的原始锁,它不会被阻塞。)

最后,如果您担心线程安全,您应该检查setNRICgetNRIC 是否是线程安全的。如果不是,我认为以上内容不能安全地处理InfoTo 对象。


请注意,除非您考虑到它所依赖的其他类以及它使用/打算使用的方式,否则您无法推断类的线程安全性。

【讨论】:

  • @hyperfkcb,请检查这个答案。我(已删除)的答案不正确。
  • @hyperfkcb - Horse 让我给你打上@标签,让你知道我们在他的回答中发现了一个缺陷,他决定删除它(至少现在是这样)。
  • 使用 synchronized + volatile 不会导致死锁。 (volatile 关键字没有显式或隐式的锁定语义。)我所说的可能的死锁可能是由于其他原因造成的。
  • 没有。那是不对的。我之前的 cmets 说static 不能保证没有死锁。我也没有说(也不是真的)非静态方法可以避免死锁。您可以使用静态方法、非静态方法或两者的混合来获得死锁。当两个或更多线程使用两个或更多锁并尝试以不同的顺序获取它们时,就会发生死锁。
  • setNRIC 不需要是线程安全的,因为它是在新创建的本地对象上调用的。但是关于 OP 的代码有一个特殊的地方,sNric 只被写入,从不读取,除非在sNric = retrieveDetail(); pers.setNRIC(sNric); 序列中,它可以(并且应该)使用局部变量来代替。那么,getInfo() 就不需要synchronizedsetInfo 方法无论如何是完全没有意义的,所以 getNRIC() 是否是线程安全的无关紧要。 retrieveDetail() 是否需要同步取决于它是否依赖于问题中未显示的一些可变状态。
【解决方案2】:

您没有真正为某个答案提供足够的信息和代码详细信息。但我会试一试。

AtomicReference

引用comment above by scottb

共享变量的线程安全是关于状态转换的可见性原子性

我经常更喜欢使用the Atomic… classes 来解决可见性和原子性问题。这些类可以提供volatilesynchronized 的替代方案。

在这种情况下,我们可以使用AtomicReference 类作为其有效负载保存对您当前所需的String 值的引用。请注意,我们将其标记为final,因为对AtomicReference 对象本身的引用永远不会改变。它的有效负载,一个将我们带到所需String 对象的引用(指针),确实发生了变化。在某一时刻它可能指向String"dog",而稍后它可能指向String"cat"。但是String 的容器始终是相同的AtomicReference 对象,它是包含文本的包装器,包含String 对象。

如果你的 InfoTO 类看起来像这样:

package work.basil.example;

public class InfoTO
{
    private String nric ;

    public String getNric ( ) { return this.nric; }

    public void setNric ( String nric ) { this.nric = nric; }
}

...那么您的DaoImpl 可能看起来像这样:

package work.basil.example;

import java.util.concurrent.atomic.AtomicReference;

public class DaoImpl
{
    private AtomicReference < String > sNric;

    private void setInfo ( InfoTO pers )
    {
        this.sNric.set( pers.getNric() );
    }

    public InfoTO getInfo ( )
    {
        InfoTO pers = new InfoTO();
        String s = retrieveDetail();
        pers.setNric( s );
        return pers ;
    }

    private String retrieveDetail ( )
    {
        return this.sNric.get();
    }
}

你的台词:

        sNric = retrieveDetail();
        pers.setNRIC(sNric);

……对我来说没有意义。您使用字段来保存临时值。所以我用一个局部变量代替。

您的retrieveDetail 方法对我来说毫无意义。您似乎从调用数据库返回的字符串值与您在字段sNric 中缓存的字符串值完全相同。所以我改变了那个方法来访问缓存字段sNric。这似乎更符合您的预期逻辑,更重要的是,它显示了 AtomicReference 的 getter 和 setter 的作用。

当然,正如其他人所说,在您没有向我们展示的大量代码中,您很可能还存在其他线程安全问题。

【讨论】:

  • 你倒退了。整个缓存毫无意义,因为缓存的值从未被使用过,正如您正确注意到的那样,传递给setNric 的值总是源自之前对retrieveDetail() 的调用。但是删除实际的数据库操作并不是解决办法。除此之外,还需要初始化一个AtomicReference 变量。
猜你喜欢
  • 1970-01-01
  • 2018-08-21
  • 2019-08-20
  • 1970-01-01
  • 2015-08-13
  • 1970-01-01
  • 1970-01-01
  • 2011-03-26
  • 2010-11-27
相关资源
最近更新 更多