Java关键字(九)——volatile

 ​​synchronized​​​ 关键字,synchronized 是jdk1.5提供的线程同步机制,可以用来修饰代码块,修饰普通方法,修饰static声明的静态方法,能够保证原子性、可见性、有序性,在jdk1.5,synchronized 是一个重量级的同步机制,线程挂起和阻塞都要从用户态转入内核态,比较耗性能,但是在jdk1.6时,进行了一系列的优化,比如自旋锁,锁粗化,锁消除等,使得其性能有了很大的提升,关于锁的这些优化,我在​​锁详解​​ 这篇文章也有详细的介绍。

  通常,对于新人来讲,不管三七二十一,对于需要同步的处理,直接上 synchronized 准没错,但是JVM还为我们提供了另外一个轻量级的线程同步机制——volatile。可能很多人没有在代码中实际使用过这个关键字,但其实,如果你研究过jdk并发包,这个关键字还是使用挺多的,下面我们就来详细介绍这个关键字。

1、可见性

  通过 volatile 关键字修饰的变量,能够保证变量的可见性。

  也就是说,当一个线程修改了被volatile修饰的变量,修改后的值对于其他线程来说是立即可以知道的。

  注意:这里有个误区,可见性并不是原子性。

  对于如下代码:

1 package com.ys.algorithmproject.leetcode.demo.concurrency.volatiletest;
2
3 import java.io.InputStream;
4 import java.util.stream.IntStream;
5
6 /**
7 * Create by YSOcean
8 */
9 public class VolatileTest implements Runnable{
10 public static volatile int count = 0;
11
12
13 @Override
14 public void run() {
15 for (int i = 0; i < 100000; i++) {
16 count++;
17 }
18 }
19
20 public static void main(String[] args) throws Exception{
21 VolatileTest vt = new VolatileTest();
22 Thread t1 = new Thread(vt);
23 Thread t2 = new Thread(vt);
24 t1.start();
25 t2.start();
26 t1.join();
27 t2.join();
28 System.out.println(count);
29 }
30 }

  代码逻辑很简单,启动两个线程,分别运行run方法,run方法里面循环100000次count++,通过join()方法等待这两个线程执行完毕后,打印 count 的最终值。

  注意这个 count 是用 volatile修饰的。

  我们说volatile 能够保证可见性,一旦 volatile 被修改了,别的线程也能马上知道,所有很明显这段代码运行结果是 200000。

  没错,这个答案是错的,是不是很意外,结果应该小于等于200000。问题就出现在 count++ 这个操作不是原子性。

  我们可以通过 javap 命令查看这个run方法的汇编指令:

   

Java关键字(九)——volatile

  虽然 count++ 在我们程序员看来,就一条代码,但实际上在字节码层面发生了4条指令。首先 getstatic 指令,表示从寄存器中取出 count 的值并压到栈顶,iconst_1 指令表示把int类型1压到操作栈顶;iadd 表示执行加法操作;最后putstatic指令表示将修改后的count写回到寄存器(由于被volatile修饰,这时候直接写回内存)。

  这就存在一个问题,线程1执行第一条指令把 count 值取到操作栈顶时,虽然 volatile 指令能够保证此时的 count 是正确的,但是在执行 iconst_1和iadd指令时,有可能线程2已经执行了count++所有指令,这时候线程1取到的count值就变成过期的数据了,这就会造成问题。

2、禁止指令重排序

  禁止指令重排序是 volatile 关键词的第二个作用。

  为了使得处理器内部的运算单元尽量被充分利用,提高运算效率,处理器可能会对输入的代码进行排序。比如 A,B,C三条按顺序运行的代码,经过指令重排后,可能是 B,A,C的顺序来执行。

  通过 volatile 修饰的变量,在进行指令优化时, 要求不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

3、总结

  这里我们对比一下volatile 关键字和 synchronized 关键字。

  1、关键字volatile是线程同步的轻量级实现,synchronized 是重量级实现,但是我们并不能说 volatile 一定比 synchronized 性能好,因为Java虚拟机对锁进行了偏向锁,自旋锁,锁消除,锁粗化等一系列优化措施。所以我们很难说 volatile性能肯定比synchronized要好;

volatile只能修饰变量,而synchronized可以修饰方法、代码块等。

多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。

volatile能保证数据的可见性,但不能保证数据的原子性;而synchronized可以保证原子性,也可以保证可见性(通过JMM)。关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

发表评论

相关文章