假设我有一个像这样的共享对象:
class Shared {
private Value val;
//synchronized set
public synchronized void setValue(Value val) {
if (this.val == null) {
this.val = val;
} else {
throw new IllegalStateException();
}
}
//unsynchronized get
public Value getValue() {
return this.val;
}
}
如果我在应用程序初始化期间有一个单独的线程设置值,在任何其他线程有机会读取之前,并且没有任何东西再次更改该值,那么读取未同步的值是否安全,或者我是否存在其他线程永远不会看到设定值的风险(因为变量不是volatile
并且不能保证刷新到主存储器)?
想象一下,在Web应用程序的上下文中,这个在Servlet初始化期间进行设置。我不知道此时是否已经创建了其他线程,因为这是容器的工作。但我假设一个线程拉动将处理将来创建的未来请求。
如果不安全,有没有一种方法可以安全地初始化价值,而不必为每次读取付出代价,即使价值永远不会改变?即有没有办法只刷一次值?
此外,这不正是例如春天一直都在做什么?在初始化容器时,会发生对单例的各种不同步设置:通过setter注入bean,@PostConstruct
初始化器触发等。一旦完成,请求被接受并且不会进行任何修改。如果这不安全,是不是每个单独的方法都不需要同步?
答案 0 :(得分:2)
取决于。
在编写值和读取它的线程之间有很多操作可能会创建之前发生的关系,以保证值的存在。
我将解决Spring MVC(以及任何Servlet应用程序)的情况。 Spring MVC通常会创建两个ApplicationContext
个实例:一个在ContextLoaderListener
内,一个在DispatcherServlet
内。
在典型的Servlet容器上,ContextLoaderListener
和DispatcherServlet
将在同一个线程上按顺序初始化。然后,容器将启动线程以侦听连接并提供请求。
在非典型容器上,您仍然可以依赖(Servlet specification,参见 2.3.2初始化章节)ContextLoaderListener
和DispatcherServlet
必须是在它可以接收请求之前完全初始化(因此是你的Spring上下文)。
初始化完成后,容器将启动服务线程and since
在线程中对
start()
的调用发生在启动线程中的任何操作之前。
您的价值将变得可见。否则,初始化线程将通过其他机制(可能是CountDownLatch
)通知服务线程,该机制提供其自己的先发生关系。
假设您只从一个线程设置该值并且您再也不会更改它,您甚至不需要设置器上的synchronized
。
在关系之前寻找这些事情,你会没事的。显然,如果你不想,那么volatile
解决方案就可以了。如果您的应用程序逻辑类似于Servlet容器(或者是Spring MVC应用程序),则不需要synchronized
或其他volatile
。关键部分是
在任何其他线程有机会阅读它之前,没有任何东西可以再次改变该值
为了防止其他线程读取该值,您可能会有一个通知机制,该机制已经添加了在正确发布您的值之前发生的关系。
答案 1 :(得分:0)
这是一场数据竞赛,所以不安全。
这是一个数据竞争,因为你有两个线程,其中一个写入一个值,另一个读取它,而没有它们之间的同步 1 。当您使用synchronized
关键字时,当第一个线程释放锁 2 后,一个线程获得锁定时,同步就会出现。
由于getValue()
方法未同步(因此从未获取锁定),因此它与任何setValue(...)
调用之间没有同步。您还可以通过将字段标记为volatile
来提供数据同步:来自易失性字段的读取与之前的任何写入同步 - 3 以及其他技术。对线程安全发布的完整记录过于宽泛,无法在此处给出答案。
因为您有数据竞争,所以值会以非原子方式从一个线程发布到另一个线程。假设你做了:
Value v = new Value();
v.setName("Bond");
v.setId(7);
Shared.setValue(v);
然后有人可以调用getValue()
并查看名称为“Bond”但其id仍为默认字段值(0)的对象。它甚至可以看到一个空名称和一个id为7,即使代码似乎暗示如果设置了id,那么名称必须是。数据竞赛会让您遇到一系列微妙的,不直观的错误。
<子> 1。 JLS 17.4.5:“当一个程序包含两个冲突的访问(第17.4.1节)时,这些访问不是由先发生过的关系排序的,而是说它包含一个数据竞争。”
<子> 2。 JLS 17.4.4“监视器上的解锁操作 m 与 m 上的所有后续锁定操作同步。
<子> 3。还有17.4.4
答案 2 :(得分:0)
这不安全。
您需要设置字段volatile
:
private volatile Value val;
允许其他线程缓存自己的非易失性字段副本,因此一个线程更改它们可能不会被其他线程“看到”。
volatile
强制所有线程立即“看到”对字段的更改(实际上,它强制线程不使用缓存副本)。
答案 3 :(得分:0)
如果有一种方法来初始化构造函数中的元素,并使其同时为final
和不可变,那么构造函数的结尾将建立一个发生之前的关系。构造对象后启动的所有线程,读取将是线程安全的。如果在构造之后的任何时间初始化元素,或者它不是final
或不是不可变的,那么您必须同步访问。 volatile
将保护您免受指针不可见的更改,但仅限于使引用的对象线程安全。