一、并发编程的三个重要特性
- 原子性
- 有序性
- 可见性
-
原子性
指一次或多次操作,要么都成功,要么都失败 -
有序性
执行顺序有序 -
可见性
一个线程对共享变量的修改,其他线程可以看到这个修改
二、volatile的定义
在多线程并发编程中,synchronized和volatile关键字都扮演着重要的角色,volatile也被视为轻量级的“synchronized”,它在多处理器开发中保证了共享变量的可见性。如果volatile关键字使用恰当的话,会比synchronized的使用更加高效,因为它不会引起线程上下文的切换和调度。
Java语言规范第三版中对volatile关键字的定义如下:
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量
如果一个共享变量被声明为volatile(volatile的英文本身含义就是不稳定的、易变的),说明这个共享变量本身被JVM标识成了不稳定的,易改变的,那么,JVM线程内存模型(不是真实存在,而是主内存和线程工作内存之间的一种同步协议)就会确保,所有线程看到这个变量的值是一致的。
三、volatile关键字是如何保证可见性的
有volatile关键字修饰的共享变量,在进行写操作的时候,会多出一条lock前缀指令,该指令在多核处理器下会发生两件事:
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
也就是说,当对volatile关键字修饰的共享变量进行写操作的时候,JVM会向处理器先发一条lock指令,将这个变量所在的缓存行的数据写回到内存。在多处理器的情况下,其他处理器缓存的数据还是旧的,再执行计算操作就会有问题,所以,为了保证各个处理器的缓存是一致的,就会实现缓存一致协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存数据是否已经过期,当处理器发现缓存的内存地址被修改,就会将当前缓存的数据设置为无效状态,当这个处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器的缓存中。
四、volatile关键字和synchronized关键字的区别
①使用上的区别:
volatile关键字只能用于修饰实例变量或者类变量,且修饰的变量可以为null,但volatile关键字不能用于修饰方法、方法参数、局部变量和常量
synchronized关键字不能用于修饰变量,只能用于修饰方法或者代码块,且修饰代码块的对象不能为null。
②对原子性的保证:
volatile关键字能保证有序性和可见性,但无法保证原子性
synchronized关键字是一种排他机制,能保证代码的原子性
③对可见性的保证:
volatile关键字和synchronized都可以保证共享资源在多线程间的可见性,但实现机制完全不同。
synchronized关键字借助于JVM指令monitor enter和monitor exit 通过排他方式使得同步代码串行化,在monitor exit时所用共享资源都将会被刷新到主内存中。
volatile关键字则使用机器指令(偏硬件)“lock”的方式迫使其他线程工作内存中的数据失效,需要重新到主内存中加载。
④对有序性的保证:
volatile关键字机制JVM编译器以及处理器对其进行重排序,实现有序性的保证
synchronized关键字是以程序串行化执行换来的,在synchronized关键字所修饰的代码块中,代码指令也会发生重排序的情况,如下:
synchronized(this) {
int x = 10;
int y = 20;
x++;
y = y + 1;
}
x和y之间没有数据依赖关系,所以谁也执行,谁后执行没有关系,但是x++必然是在int x = 20之后(有数据依赖关系),y = y + 1也必然是在int y = 20之后编译(有数据依赖关系)。
⑤其他
volatile关键字不会使线程陷入阻塞
synchronized关键字会使线程进入阻塞状态
网友评论