【问题标题】:How the example from Java Concurrency in practice is thread safeJava Concurrency 在实践中的示例如何是线程安全的
【发布时间】:2018-05-22 09:06:27
【问题描述】:

下面是Java Concurrency in Practice(Listing 2.8)一书中的一个线程安全类的示例。

我的问题是下面的类是如何线程安全的?

例如如果两个线程Thread A和Thread B进入CachedFactorizer的服务方法。 Thread B 后跟 Thread A。现在如果Thread A 正在执行第一个同步块并且Thread B 显然等待对象的内在锁。如果Thread B 在Thread A 进入第二个同步块之前进入第一个同步块,它将查看一个过时的值,并且这种情况可能被称为Race Condition。

那么,我的理解就在这里吗?还是我对并发缺乏一些基本的了解?

@ThreadSafe
public class CachedFactorizer implements Servlet {

    @GuardedBy("this") private BigInteger lastNumber;

    @GuardedBy("this") private BigInteger[] lastFactors;

    @GuardedBy("this") private long hits;

    @GuardedBy("this") private long cacheHits;

    public synchronized long getHits() { return hits; }

    public synchronized double getCacheHitRatio() {

        return (double) cacheHits / (double) hits;

    }


    public void service(ServletRequest req, ServletResponse resp) {

        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        if (factors == null) {
            factors = factor(i);
            synchronized (this) {
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}

【问题讨论】:

  • 你能具体说明你关心的陈旧变量吗?
  • 所有状态变量。hits、factors、lastfactors 和 cachehits。
  • 你不是第一个质疑这个例子的,虽然这个例子是关于使用clone():stackoverflow.com/questions/12034370/…

标签: java multithreading concurrency


【解决方案1】:

这个类是线程安全的,因为所有共享变量都是在同步块中访问的,在这种情况下没有数据竞争:

“当一个程序包含两个冲突的访问(第 17.4.1 节),这些访问没有按发生前的关系排序时,就说它包含数据竞争。https://docs.oracle.com/javase/specs/jls/se9/html/jls-17.html”

问题是从业务角度来看,这种行为是否有效,例如。如果两个线程在第一个同步块之后相遇,它们可以以不同的顺序执行第二个块。

【讨论】:

  • 是的,如果两个线程在第一个同步块之后相遇,它们可以以不同的顺序执行第二个块,这可能导致'lastNumber'和'lastFactors'的值不同。 --- 这就是重点。
【解决方案2】:

在第一个块退出后唯一使用的是因子,它被克隆(),因此不受进入第一个或第二个块的任何其他线程的影响。

【讨论】:

    【解决方案3】:

    如果我删除两个 clone() 有什么区别?例如:因子 = lastFactors.clone();我认为它仍然有效。

    public void service(ServletRequest req, ServletResponse resp) {
    
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors; //not cloned
            }
        }
        if (factors == null) {
            factors = factor(i);
            synchronized (this) {
                lastNumber = i;
                lastFactors = factors;//not cloned
            }
        }
        encodeIntoResponse(resp, factors);
    }
    

    【讨论】:

    • 1.lastFactores 仅更改为另一个数组对象。不修改数组对象的内容。所以不需要克隆2。第一个同步是读取两个共享变量的原子操作,第二个同步是写入两个变量的原子操作。
    猜你喜欢
    • 1970-01-01
    • 2017-10-08
    • 1970-01-01
    • 2016-02-19
    • 1970-01-01
    • 2014-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    相关资源
    最近更新 更多