synchronized -> 同步锁
所有加上synchronized修饰的方法 或 代码块,在同一个时刻,只有一个线程能访问
volatile -> 内存可见性,禁止指令重排序
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的.
如同上面所讲的,volatile
只是保证从内存加载到线程工作内存的值是最新的,那这里会出现什么问题呢?无法保证操作的原子性
看一下代码:
public class VolatileLearn {
public static int count = 0;
public static void add(){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
count++;
}
public static void main(String[] args){
for (int i=0; i< 1000; i++){
new Thread(() -> {
VolatileLearn.add();
}).start();
}
try {
Thread.sleep(50000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
System.out.println("运行的结果:count = " + VolatileLearn.count);
}
}
}
// 运行的结果:count = 990
上面的例子中,自增操作是不具备原子性的,可分为: 读取变量的原始值、进行加1操作、写入工作内存,volatile
关键字只保证了读取变量的原始值操作的原子性,并没有保证进行加1操作、写入工作内存的原子性,也就是说会出现两个线程读取到的值(主内存)相同,线程对工作内存操作后的结果一致,最后写入主内存只+1了。
虚拟机在保证执行结果准确性的同时,会对指令进行一定的重排,(来最大性能的发挥CPU?)。
对于new Class来讲,可以分为:
1)创建对象实例
2)执行类的构造函数
3)将实例对象指向分配的内存空间
一般的执行顺序是1-2-3,也可能存在1-3-2这个顺序,如果没有volatile字段修饰,那可能存在某一个线程访问获取这个实例对象,恰好执行到1-3,这时候对象并没有执行完构造函数,是会出问题的。在这个实例对象的变量前添加volatile字段,可以避免指令被重排。
并发编程的三个概念:
- 原子性
- 可见性
- 有序性
更全面理解参考