简单记录自己理解的volatile关键字
简单概念
计算机执行程序时,每条指令都是由CPU执行的,指令的执行又涉及到数据的读取和写入。程序的临时数据是在主存(物理内存)中,频繁的读取和写入就会产生一个问题,从主存中读取和写入的时间要比CPU执行指令的时间要长的多,大大降低计算机的执行效率,于是CPU里就有了高速缓存。
i = i + 1;
比如上式,线程先将i值从主存读入高速缓存中,计算i+1,计算完成后再写回主存。
单线程时上面的计算和描述是没有问题的,如果两个线程同时执行这段代码,假如i的初始值是0,那么理论上我们期望得到i的值是2,真的 是这样吗?
有种情况可能得到的值是1,两个线程分别来读取i的值到缓存中,线程1计算完成后写入主存i=1,此时线程2中的i还是0,计算完成后i也是1,然后线程2将i=1写入主存。
最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。
并发编程的常见问题
1 . 原子性
举个栗子:i++, i=2, i=x+y 这三个操作中,其实只有i=2是原子操作,i++等价于i=i+1,就回到上面讲的那个问题了,看代码
static int count = 0; public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { try { for (int i=0; i<10000; i++) { increment(); } }catch (Exception e){ e.printStackTrace(); } } }; Thread thread1 = new Thread(runnable, "thread1"); Thread thread2 = new Thread(runnable, "thread2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(count); } private static void increment(){ count++; }
你会发现输出的值始终小于20000,原因之一是count++不是原子操作。
若想执行得到正确的值只需要使用synchronized关键字修饰increment()方法即可
private static synchronized void increment(){
count++;
}
或者使用其他锁,比如ReentrantLock,还有AtomicInteger
2 . 可见性
上面的问题也说明了线程之间的缓存可见性
volatile关键字的作用之一就是使共享变量在缓存中被修改时,及时将数据刷入主存上,并通知其他线程该变量的缓存无效,需要重新在主存中读取
synchronized关键字和Lock也可以解决缓存不一致问题,因为synchronized只允许一个线程执行,所以就不存在可见性的问题了
3 . 有序性
java内存模型为了提高执行效率,会对指令进行重排序,不保证代码执行顺序和源代码一致,但保证代码执行结果和顺序执行结果一致。
volatile关键字的作用
1.保证可见性,即被volatile修饰的变量,在线程的高速缓存中被修改后,会及时刷入主存,并通知其他线程该变量的缓存失效,使用时重新在主存中读取
2.禁止进行指令重排序(即volatile修饰的变量,前面的指令不会被排到该变量后面,后面的一样不会被排带前面去)
记录一下,顺便帮助自己捋一遍思路,记录过程中发现自己也有很多概念模糊的地方,收获很多,关于volatile的文章,比我记录详细的文章很多,这篇文章主要为自己写。