要理解volatile关键字,我们首先需要理解并发编程的三个重要特性:原子性、有序性和可见性
一、原子性
原子性是指在一次操作或者多次操作中,要么所有的操作全部得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。
1.x=10;原子性
2.y=x;非原子性:执行线程从主内存读取x的值,再在执行线程的工作内存中修改y的值为x,然后将y写回主内存,这两步单独是原子性的,但和在一起就不是原子性的了
3.y++,非原子性。自增操作包括三个步骤:读取值、自增、写回主内存。三个步骤单独是原子性的,但合起来不再是原子操作
二、可见性
可见性是指当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。
三、有序性
有序性是指程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序,但是他会保证程序的最终运算结果是编码所期望的那样。
volatile关键字的语义
1.保证了不同线程之间对共享变量操作时的可见性,也就是说当一个线程修改volatile修饰的变量,另外一个线程会立即看到最新值
2.禁止对指令进行重排序操作
volatile保证了可见性、有序性,但并不保证原子性,我们通过一段代码来分析一下
public class VolatileTest
{
private static volatile int i=0;
private static final Latch latch=new CountDownLatch(10);
private static void inc() {
i++;
}
public static void main(String[] args) throws InterruptedException
{
for(int i=0;i<10;i++)
{
new Thread(()->
{
for(int x=0;x<1000;x++)
{
inc();
}
latch.countDown();
}
}).start();
}
latch.await();
System.out.println(i);
}
上面这段代码创建了十个线程,每一个线程都对共享变量i执行1000次自增操作,最后的结果会是10000吗?答案是否定的。
在上面我们已经提到过自增操作并不是原子性的。
1.假设此时i的值为100,线程A要对i进行加1操作,线程A从主内存中读取了100到工作内存,但此时由于cpu时间片调度的关系,执行权切换到了线程B
2.线程B同样需要对i进行自加操作,由于线程A还未对i进行任何修改,线程B读取到的依然是100
3.线程B对i进行了加1操作,此时线程B工作内存中i的值为101,但还未写回主内存。
4.这是CPU时间片的调度又将执行权交给了线程A,A线程直接对工作内存中的100加1,由于线程B还未更改主内存的值,所以这里的100不会失效
5.线程A将101写回主内存
6.线程B将101写回主内存
这样,两次运算实际上只对i进行了一次数值修改变化
volatile和synchronized
1.使用上的区别:volatile只能用于修饰实例变量或者类变量,不能用于修饰方法、方法参数、局部变量和常量
synchronized不能用于对变量的修饰,只能用于修饰方法或者代码块
2.volatile不能保证原子性
synchronized可以保证原子性
3.两者均保证可见性和有序性
4.volatile不会使线程陷入阻塞
synchronized会使线程陷入阻塞