
文章图片

Java中的volatile关键字特征和使用场景
1.不能保证原子性首先需要注意一点 , 那就是volatile无法保证原子性 。
一个很简单的实验可以证明这点:
public class Test { public volatile int inc = 0; public static void main(String[
args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for (int j = 0; j < 1000; j++) {
test.inc++;
;
.start();
//保证前面的线程都执行完 , eclipse是1 , IDEA是2 , 因为IDEA多了个监控线程
while (Thread.activeCount() > 2) {
Thread.yield();
System.out.println(Thread.activeCount());
System.out.println(test.inc);
按理说结果是10000 , 但是真正试了就会发现 , 没几次能到10000:
可以看到 , 不仅是有些误差 , 有时候甚至差了好几千 。
这就是因为volatile没有保证原子性的缘故 。
虽然它保证了可见性和有序性 , 让每个线程都能获取最新的变量 , 但是它不能保证同一时间只有一个线程能执行增加操作 。
前面说了inc++ , 这个操作并不具有原子性 。 自增操作 , 首先是读取了inc的值(从缓存中读取 , 并非从主存中读) , 然后进行加1运算 , 再将结果存回主存 , 这是三步操作 。 可见性的保证 , 是让他在步骤2之后能立即将值存回主存 , 并使其他线程中的该变量缓存失效 。
但是没有保证原子性的情况下 , 就会有这种可能性:
①线程1从自己的工作缓存中读取了inc的值 , 为0
②线程2从自己的工作缓存中读取了inc的值 , 为0 , 然后阻塞一会
③线程1对inc进行加一运算 , 得到结果1
④线程1将结果1存回主存 , 并将其他线程中的该变量的缓存设为失效
——到这里 , 线程1已经完成了他的一次操作 , 按理说不是设为失效了吗 , 为什么线程2还是会使用错误的值 。 因为线程2已经完成了【读取】这个操作了 , 只有在它读取前 , 将变量失效 , 它才能在读取时发现异常并去主存重新获取 。
⑤线程3从自己的工作缓存中读取inc的值 , 发现已经失效 , 重新从主存获取到了变量inc的最新值1 。
——这时候线程3能取到正确的值 , 因为它还没有进行读取这个操作 。
⑥线程2对inc进行加一运算 , 得到结果1
⑦线程2将结果1存回主存 , 并将其他线程中的该变量的缓存设为失效
问题就这样产生了 , 线程2自顾自的把错误的值设置了回去 , 线程1的操作相当于白费了 。
2.volatile的使用场景volatile的效率显然是要高于synchronized的 , 因为后者会阻止其他线程访问 , 相当于强制变成单线程模式 。 但是volatile却不能完全代替synchronized , 因为它不能保证原子性 。
有时候我们可以结合两者使用 , 这样可以缩减synchronized锁住的范围 。
- 太空中的真菌毒素是否危害宇航员健康?科学家:尚不明确
- 科学家:人类只是一片巨大的“空洞虚无”中的一粒微尘!
- Java|干翻小米,红米又出超大杯旗舰,120W+2K直屏+双旗舰芯
- 百度地图|Java程序员应知应会之Maven和Gradle的区别
- javascript|穿戴设备将迎变革,OPPO又增新专利,能提升模式切换效率!
- Java|Gamamobi CEO黄继德:我们的元宇宙游戏不担心体验落差
- 文艺评论丨科幻题材崛起与Z世代审美观照中的双向奔赴
- Java|美国传来新消息,ASML态度也变了,这一切竟如此之快
- Java|李佳琦消失扯下阿里“遮羞布”,原来淘宝根本就没有护城河
- Java|我做公众号终于不亏钱了
