java并发(四)volatile的使用

解决可见性和有序性,不能保持原子性
这是与volatile的使命有关的,创造它的背景就是在某些情况下可以代替synchronized实现可见性的目的,规避synchronized带来的线程挂起、调度的开销

防止重排序

线程单例模式的实现——双重检查加锁(DCL)

 1 package com.paddx.test.concurrent;
 2 
 3 public class Singleton {
 4     public static volatile Singleton singleton;
 5 
 6     /**
 7      * 构造函数私有,禁止外部实例化
 8      */
 9     private Singleton() {};
10 
11     public static Singleton getInstance() {
12         if (singleton == null) {
13             synchronized (singleton) {
14                 if (singleton == null) {
15                     singleton = new Singleton();
16                 }
17             }
18         }
19         return singleton;
20     }
21 }

分析为什么在对象singleton前面加上volatile关键字
对象创建的3个过程(1)分配内存空间(2)初始化对象(3)将内存空间的地址赋值给对应的引用

因为操作系统可以对指令进行重排序,上面的过程顺序可能为:
(1)分配内存空间(2)将内存空间赋值给对应的引用(3)初始化对象

重排序容易将未初始化的对象暴露出来,所以需要设置成volatile类型的变量

有序性的实现

happen-before规则

  • 程序次序规则:在一个线程里,按照代码顺序,书写在前面的操作优先发生于书写在后面的操作。准确的是控制顺序而不是代码顺序,因为要考虑分支、循环结构
  • 监视器锁规则:一个unlock操作先行发生于后面对同一个对象锁的lock操作。这里强调的是同一个锁,而“后面”指的是时间上的先后顺序,如发生在其他线程中的lock操作。
    -volatile变量规则:对一个volatile变量的写操作发生于后面对这个变量的读操作,这里的“后面”也指的是时间上的先后顺序。
  • 线程的start() 方法 happen-before 该线程所有的后续操作。
  • 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。

重排序规则表

在这里插入图片描述
解释:

  • 当第二个操作是volatile 写时,不管第一个操作是什么,都不能重排序。
  • 当第一个操作是volatile 读时,不管第二个操作是什么,都不能重排序。
  • 当第一个操作是volatile 写,第二个操作是volatile 读时,不能重排序

实现可见性

可见性问题:主要指一个线程修改了共享变量值,而另一个线程却看不到,volatile可以解决此问题
每个线程拥有自己的告诉缓存区——线程工作内存

1 package com.paddx.test.concurrent;
 2 
 3 public class VolatileTest {
 4     int a = 1;
 5     int b = 2;
 6 
 7     public void change(){
 8         a = 3;
 9         b = a;
10     }
11 
12     public void print(){
13         System.out.println("b="+b+";a="+a);
14     }
15 
16     public static void main(String[] args) {
17         while (true){
18             final VolatileTest test = new VolatileTest();
19             new Thread(new Runnable() {
20                 @Override
21                 public void run() {
22                     try {
23                         Thread.sleep(10);
24                     } catch (InterruptedException e) {
25                         e.printStackTrace();
26                     }
27                     test.change();
28                 }
29             }).start();
30 
31             new Thread(new Runnable() {
32                 @Override
33                 public void run() {
34                     try {
35                         Thread.sleep(10);
36                     } catch (InterruptedException e) {
37                         e.printStackTrace();
38                     }
39                     test.print();
40                 }
41             }).start();
42 
43         }
44     }
45 }

直观上说,这段代码的结果只可能有两种:b=3;a=3 或 b=2;a=1。不过运行上面的代码(可能时间上要长一点),你会发现除了上两种结果之外,还出现了第三种结果:
b=3,a=1
原因:
原因就是第一个线程将值a=3修改后,但是对第二个线程是不可见的,所以才出现这一结果。
如果将a和b都改成volatile类型的变量再执行,则再也不会出现b=3;a=1的结果了。
在这里插入图片描述

可见性实现原理(重要)

  • 修改volatile变量时会强制将修改后的值刷到主存中

  • 改volatile变量后会导致其他线程工作内存中对应的变量值失效,因此,再读取该变量值的时候需要重新从主存中读取。

不能保证原子性

 1 package com.paddx.test.concurrent;
 2 
 3 public class VolatileTest01 {
 4     volatile int i;
 5 
 6     public void addI(){
 7         i++;
 8     }
 9 
10     public static void main(String[] args) throws InterruptedException {
11         final  VolatileTest01 test01 = new VolatileTest01();
12         for (int n = 0; n < 1000; n++) {
13             new Thread(new Runnable() {
14                 @Override
15                 public void run() {
16                     try {
17                         Thread.sleep(10);
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                     test01.addI();
22                 }
23             }).start();
24         }
25 
26         Thread.sleep(10000);//等待10秒,保证上面程序执行完成
27 
28         System.out.println(test01.i);
29     }
30 }

在这里插入图片描述
可能每个人运行的结果不相同。不过应该能看出,volatile是无法保证原子性的(否则结果应该是1000)。原因也很简单,i++其实是一个复合操作,包括三步骤:读取i的值。对i加1。将i的值写回内存。

volatile与synchronized的区别

当多个线程进行操作共享数据时,可以保证内存中的数据可见
相对于synchronized是一种较为轻量级的同步锁
注意:
volatile不具备互斥性
volatile不能保证变量的原子性
参考博客
https://blog.csdn.net/qq_39054532/article/details
https://www.cnblogs.com/paddix/p/5428507.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值