中年|揭秘JMM、Synchronized、Volatile之间的关系( 四 )


其实关于volatile的特性 , 我们在介绍JMM的时候都已经了解很大一部分了 。 但是我们也说过volatile是不保证原子性的 , 这里我们还是需要用代码来展示的 。
我们开十个线程 , 每个线程都对被volatile修饰的共享变量进行1000次自增操作 。

public class Demo01 { private volatile int num = 0 private void increase(){ num++ } public static void main(String[] args) throws InterruptedException { final Demo01 demo01 = new Demo01() for(int i = 0 i &lt 10 i++){ new Thread(()-&gt{ for(int j = 0 j &lt 1000 j++){ demo01.increase() } }).start() } //保证在主线程结束之前 , 其他线程执行完毕 TimeUnit.SECONDS.sleep(2) System.out.println(demo01.num) }}
我们执行之后就可以看 , 打印的数有时候并不是我们想要的10000 , 而且接近这个数 。 这充分体现了volatile并不能保证原子性 。 而要解决这个问题的话 , 就需要用加锁或者用synchronized来修饰方法 。
还有一种情况比较经典的如下:
public class Demo02 { private int value public int getValue() { return value } public void setValue(int value) { this.value = http://news.hoteastday.com/a/value }}
这里在并发下是不安全的 , 因为没有适当的同步措施 。 就会导致内存不可见 , getValue有时候取到的值的之前调用了setValue , 但是还没有刷回主内存 。 这里我们就可以用到synchronized或者是volatile:

public class Demo02 { private int value public synchronized int getValue() { return value } public synchronized void setValue(int value) { this.value = http://news.hoteastday.com/a/value }} public class Demo02 { private volatile int value public int getValue() { return value } public void setValue(int value) { this.value = http://news.hoteastday.com/a/value }}
在这里这两种方法是等价的 , 都可以解决内存可见性的问题 。 但是需要注意的是synchronized内置的锁是独占锁 , 这个时候同时只能有一个线程调用getValue方法 , 其他线程会被阻塞 , 同时也会存在线程上下文切换和线程重新调度的开销 , 这也是使用锁不好的地方 。
到了这里我们也了解到了volatile关键字的两个比较重要的特性 , 那么我们如何将两个特性用在该用的地方呢?也就是下面最后的一个问题
3.1 那一般在什么时候使用volatile关键字呢?
写入变量值不依赖变量的当前值 。 因为如果依赖当前值 , 将是一个获取-计算-写入三步操作 , 三步操作不是原子性的 , 而volatile不保证原子性 。
读写变量值时没有加锁 。 因为加锁本身已经保证了内存可见性 , 这个时候再用volatile无异于多此一举 。 而锁也多了volatile没有的原子性 。
【来源:丁影视大看点】
声明:转载此文是出于传递更多信息之目的 。 若有来源标注错误或侵犯了您的合法权益 , 请作者持权属证明与本网联系 , 我们将及时更正、删除 , 谢谢 。邮箱地址:newmedia@xxcb.cn


推荐阅读