部分线程安全是否使Java类线程安全?

sof*_*ver 5 java concurrency multithreading thread-safety reentrantlock

我遇到了一个声称是线程安全的Java类的例子.任何人都可以解释它是如何线程安全的?我可以清楚地看到类中的最后一个方法没有防止任何读者线程的并发访问.或者,我在这里错过了一些东西吗?

public class Account {
    private Lock lock = new ReentrantLock();
    private int value = 0;
    public void increment() {
       lock.lock();
       value++;
       lock.unlock();
    }
    public void decrement() {
       lock.lock();
       value--;
       lock.unlock();
    }
    public int getValue() {
       return value;
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*n C 6

代码不是线程安全的.

假设一个线程调用decrement然后第二个线程调用getValue.怎么了?

问题是,和之间没有"发生过"的关系.这意味着无法保证呼叫会看到结果.实际上,可能会"错过"无限期序列和调用的结果.decrementgetValuegetValuedecrementgetValueincrementdecrement

实际上,除非我们看到使用Account该类的代码,否则线程安全问题是不明确的.程序的线程安全1的传统概念是关于代码是否正确地行为,而与线程相关的不确定性无关.在这种情况下,我们没有规定"正确"行为是什么,或者确实是要测试或检查的可执行程序.

但是我对代码2的解读是有一个隐含的 API要求/正确性标准,它getValue返回帐户的当前值.如果有多个线程,则无法保证这一点,因此该类不是线程安全的.

相关链接:


1 - @ CKing的答案中的并发实践引用也通过在定义中提及"无效状态"来吸引"正确性"的概念.但是,内存模型上的JLS部分未指定线程安全性.相反,他们谈论"格式良好的执行".

2 - 此读数由OP的评论支持.但是,如果你不接受这个要求是真实的(例如因为它没有明确说明),那么另一方面是"帐户"抽象的行为取决于Account类之外的代码...这使得这是一个"漏洞的抽象".


CKi*_*ing 1

简短的回答:

根据定义,即使方法不受保护Account,也是线程安全的类geValue

长答案

从实践中的 Java 并发来看,在以下情况下,类被称为线程安全的:

对线程安全类的实例顺序或并发执行的任何一组操作都不会导致实例处于无效状态。

由于该getValue方法不会导致Account类在任何给定时间处于无效状态,因此您的类被认为是线程安全的。

Collections#synchronizedCollection的文档引起了这种情绪的共鸣:

返回由指定集合支持的同步(线程安全)集合。为了保证串行访问,对后备集合的所有访问都通过返回的集合完成是至关重要的。用户在迭代返回的集合时必须手动同步它:

 Collection c = Collections.synchronizedCollection(myCollection);
 ...   
  synchronized (c) {
  Iterator i = c.iterator(); // Must be in the synchronized block
  while (i.hasNext())
     foo(i.next());   
  }
Run Code Online (Sandbox Code Playgroud)

请注意文档如何说明集合(它是类SynchronizedCollection中命名的内部类的对象Collections)是线程安全的,但要求客户端代码在迭代集合时保护集合。事实上,iterator中的方法SynchronizedCollection不是synchronized。这与您的示例非常相似,其中Account线程安全,但客户端代码在调用时仍然需要确保原子性getValue。