Java web 开发中,有哪些需要使用 volatile 的典型场景?

2019-02-02 14:35:29 +08:00
 yamasa

大致了解 volatile 的工作原理( cpu 一致性协议等),也理解它代替不了同步原语,但对它的使用场景却不怎么熟。在 web 开发中有没有可以举例说明的几个典型场景?不用就会出问题的那种?如果不用会出现什么的 error case 能具体说明就更好了。

4428 次点击
所在节点    Java
16 条回复
lhx2008
2019-02-02 14:41:50 +08:00
延迟加载单例模式,double check + 锁
多线程库里面大量用到,不过自己写用不到
自己写可能就是一些多线程下面修改变量要考虑要不要 volatile,不过一般现在也用 atomic 包下面的
yamasa
2019-02-02 15:05:29 +08:00
@lhx2008 以前看过 atomic 一些 class 的源码。刚去大致回顾了下,atomicReference 和 atomicBoolean 在内都是基于 unsafe CAS 实现的吧,不知道其他几个 atomic class 有没有特例。开发业务代码的时候,我想不到什么 case 不去直接用这几个类而要自己造轮子。当然还是应该理解其实现原理。
Raymon111111
2019-02-02 15:14:04 +08:00
理解的原理之后就知道为什么做全局计数器的时候不能用原生的 int 和 long(long 还有分段计数的风险)
yamasa
2019-02-02 15:18:33 +08:00
@Raymon111111 这个我觉得多去看看 volatile 在 OS 层面的实现应该就很好理解了
ultimate010
2019-02-02 17:06:39 +08:00
web 开发最常见的就是双重检查的单例吧,好多都忘了写 volatile,是不安全的。然后还有可以作为多线程 while(!stop)这样的线程间共享的开关。
bobuick
2019-02-02 17:15:43 +08:00
题主问的是 web 开发
能用到的不多,Java 的 web 开发,其实就说一大篓子 static (此处不是真 static,只是说没状态),虽然你写的各种 class,Spring 帮你 IOC 了,可是跟 static 也没什么卵区别。
异步的一些场景的时候会用到,比如 servlet3 的 Async,如果你自己有个变量,然后自己放进 ThreadPool 内的异步跟此变量会有读取,可能就需要了。
luozic
2019-02-03 11:09:47 +08:00
Java io 相关的比较少,但是计算相关的大把需要考虑,特别是分布式算结果的。
yamasa
2019-02-03 12:07:07 +08:00
@luozic 能否给一些更具体的实例呢?如果是出于原子性操作的考虑,为什么不是用 atomic 包装类,或者用 unsafe 提供的 cas ?还是说分布式计算更看重可见性呢?需要使用 volatile 间接完成 happens before,以保障可见性?
rim99
2019-02-03 15:34:12 +08:00
一写多读的共享变量用 volatile 修饰,可以实现轻量级的多线程及时发布。
af463419014
2019-02-03 15:49:56 +08:00
@lhx2008 @ultimate010

内部静态类单例 了解一下

懒汉加载且线程安全,简单粗暴,锁和 volatile 都不需要

我在实际中用过的有两种
1.就是 @ultimate010 提到的 while(stop)
2.防止代码重排序
比如初始化的时候,下面这种情况
如果 inited 参数没有 volatile 修饰,可能在 init()方法中,先执行 inited=true,再执行 start=0
这样在 run()方法里执行 a=start 时,a 的值不等于 1
void init(){
start=1;
inited=true;
}

void run(){
if(inited){
int a=start;
//执行内容
}
}
lhx2008
2019-02-03 16:43:42 +08:00
@af463419014 要简单粗暴的话直接 enum 单例,只用一行额外代码
事实上,单例也用不到,人人都用 spring

还有启动器这种,本质也和单例问题一样,如果代码层级实现还是双重检查+锁,或者是其他机制。只用 volatile 没有太大意义。
lhx2008
2019-02-03 16:50:16 +08:00
@lhx2008
比如说,你提供代码,线程 A 已经进入 init(),执行到 start = 1,但是卡住了。线程 B 这个时候检查 inited,即使没有重排序,B 也是 init= false,所以又去执行 init()了。
af463419014
2019-02-03 18:12:39 +08:00
@lhx2008
首先,enum 是饿汉单例
双锁单例 和 内部静态类单例 则是懒汉且线程安全的,没有可比性


另外,执行到 start=1 卡住这种不是我想表达的问题

我想表达的是,在 init()方法中
start=1 和 inited=true 这两行代码,有可能先执行 inited=true,再执行 start=1

执行的顺序是:
1.A 线程执行 inited=true
2.B 线程判断 if(inited)成功,执行 a=start
3.A 线程执行 start=1

这种情况,B 线程会在认为已经初始化完成时,获取到错误是初始参数

详细的还原代码
https://gist.github.com/af463419014/6814e807684c46bda34349608d9f5882

输出 out 可能等于 1,也就是 in=0,inited=1 的和
这种情况就是先执行了 inited=1,但还未执行 in=10,也就是执行顺序被重排了
liangdu
2019-02-04 09:15:25 +08:00
说个上面没人说的,它的作用就是确保可见性,你说应用场景,基本都被都差不多是配合同步原语操作并发安全。但有一个场景很妙,采用了有限重试次数和延迟的方案优化同步性能。在 concurrentlinkedqueue 里面有一个用法,在确保可见性利用 volatile 读比 cas 写性能好的优势,延迟头尾指针写的操作。每少写一次就必须多读一次,这个代价是有利的,因为读性能比写好。
liangdu
2019-02-04 09:18:01 +08:00
@Raymon111111 分段问题在商业 vm 不存在,有针对性优化,加了专门针对它的逻辑约束。
NUT
2019-02-14 17:56:03 +08:00
这也是内存可见性的应用
典型应用:
1. cas 操作 上面的大佬都提到了。
2. 所需要的成员变量 只要求可见性,就不需要用 atomic 包的那些类。

volatile 只能保证可见性,也就是 happens-before 原则的一条规定。他无法保证线程安全。大概的原理是每个线程 读的时候强制从主内存读一遍。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/532658

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX