假设我有一个这样的共享对象:
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初始化期间。我不知道此时是否创建了其他线程,因为这是容器的工作。但是我假设到那时已经创建了一个线程拉取来处理将来的请求。
如果不安全,有没有一种方法可以安全地初始化值而不必为每次读取付出代价,即使值永远不会改变?i、 e.有没有办法只冲一次值?
另外,这不正是spring一直在做的吗?在初始化容器时,单例上的各种不同步设置正在发生:通过setters注入bean, @PostConstruct
初始化等一旦完成,请求被接受,没有修改发生。如果这是不安全的,难道不是每个单例方法都需要同步吗?
4条答案
按热度按时间e5nszbig1#
视情况而定。
在编写值和读取值的线程之间有许多操作,这些操作可能会创建“发生在”关系,您需要确保值存在。
我将讨论springmvc(以及任何servlet应用程序)的情况。springmvc通常创建两个
ApplicationContext
示例:一个ContextLoaderListener
一个在一个DispatcherServlet
.在典型的servlet容器上
ContextLoaderListener
以及DispatcherServlet
将在同一线程上顺序初始化。然后,容器将启动线程来侦听连接和服务请求。在非典型容器上,您仍然可以依赖以下事实(servlet规范,参见2.3.2初始化一章):
ContextLoaderListener
以及DispatcherServlet
在接收请求之前,必须完全初始化它(因此您的spring上下文)。初始化完成后,容器将启动服务线程,然后
打电话给
start()
在开始的线程中的任何操作之前发生。你的价值将会显现出来。否则,初始化线程将通过其他机制(可能是
CountDownLatch
)它提供了自己的发生前关系。假设您只从一个线程设置了该值,并且不再更改它,那么您甚至不需要
synchronized
在setter上。看看这些发生之前的关系,你会没事的。显然,如果你不想,那么
volatile
解决方法很好。如果您的应用程序逻辑类似于servlet容器(或者是springmvc应用程序),则不需要这样做synchronized
或者额外的volatile
. 关键是在任何其他线程有机会读取它之前,不会再改变值
为了防止其他线程读取该值,您可能已经有了一个通知机制,该机制已经添加了将正确发布您的值的before关系。
a0x5cqrl2#
如果有方法初始化构造函数中的元素,并使其同时
final
并且是不可变的,则构造函数的结尾与在构造对象之后启动的所有线程建立“发生在”关系,并且读取将是线程安全的。如果在构造之后的任何时候初始化元素,或者final
或者不是不变的,则必须同步访问。volatile
将保护您不受指针不可见更改的影响,但这不足以使引用的对象线程安全。r6hnlfcb3#
这不安全。
你需要进入这个领域
volatile
:其他线程可以缓存自己的非易失性字段副本,因此一个线程对它们所做的更改可能不会被其他线程“看到”。
volatile
强制所有线程立即“查看”对字段的更改(实际上,它强制线程不使用缓存副本)。ckocjqey4#
这是一场数据竞赛,所以不安全。
这是一个数据竞争,因为有两个线程,一个写一个值,另一个读一个值,它们之间没有同步。当你使用
synchronized
关键字,当一个线程在第一个线程释放锁2之后获取锁时,同步就开始了。自从
getValue()
方法不同步(因此永远不会获取锁),它和任何setValue(...)
打电话。还可以通过将字段标记为volatile
:从易失性字段的读取与之前对它的任何写入3以及其他技术同步。对线程安全出版物的全面描述太宽泛,这里无法给出答案。因为您有一个数据竞争,所以值是以非原子方式从一个线程发布到另一个线程的。假设你做到了:
可能有人会打电话来
getValue()
并查看一个名为“bond”但其id仍然是默认字段值(0)的对象。它甚至有可能看到一个空名称和一个7的id,尽管代码似乎暗示如果id被设置了,那么名称一定已经被设置了。数据竞赛会让你面对许多微妙的、不直观的错误。1.jls 17.4.5:“当程序包含两个冲突的访问时(§17.4.1)不按“发生在”关系排序的,称为包含数据竞争
2.jls 17.4.4“监视器m上的解锁操作与m上的所有后续锁定操作同步”。
3.也是17.4.4