一、volatile解析
1.计算机内部的内存模型
- 我们知道计算机内部含有内存和CPU,那么在进行计算的时候,内存读写还是太慢了,因此在内存和CPU之间还是有一个缓存cache
- 那么我们知道如果是一个共享变量的话,就会导致,内存中变量和缓存中的变量由于多线程同步不及时,也就是说,一个线程中的缓存还没来的急写入内存,此时有可能在内存中的变量被其他线程读取了。
- 解决这个问题可以:1)通过在总线加LOCK#锁的方式;2)通过缓存一致性协议
- 第一种方式效率低下,很难实现多线程;第二种方式有一个著名MSI协议,就是指当缓存写入内存之后,会向其他线程发出信号,共享变量数值已变,如果需要的话,需要重新更新。
2.并发编程的三个概念
- 原子性:一组操作要么全部执行成功,要么全部执行失败,不存在部分成功,部分失败的情况
- 可见性:一个线程对共享变量在缓存中已经更改,但是还没有来得及写入内存,此时又有一个线程访问了该变量,那么就会出现数据不一致
- 有序性:各个语句的执行其实不一定按照从上到下的顺序,但是最后个结果一定和按从上到下执行的结果一致,因为如果语句不相干,先执行哪个后执行哪个无所谓,但是语句相干,就会按照从上到下的正常顺序。
3.Java中的内存模型
- 与计算机的内存模型相似,也会有三大特性
- 原子性,这个特性,可以通过synchronnized和lock来保证
- 可见性,Java通过violate来保证,如果共享变量是有violate来修饰那么就是保证,如果改变了它的值就能保证绝对会写入内存之后,别的线程才会调用。
- 有序性,Java中存在happens-before原则来保证有序性,如果两条语句通过该原则仍然不能推断先后执行的先后顺序,那么这两个语句是不是有序的,谁先执行都说不准。
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- (1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- (2)锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- (3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- (4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- (5)线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- (6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- (7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- (8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
4.下面我们举个例子
package com.ruigege.threadFoundation1;
public class TestViolate {
public volatile int inc =1;
public static void main(String[] args) {
TestViolate test = new TestViolate();
for(int i=0;i<10;i++) {
//创建十个线程,对inc进行自增操作
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<10000;j++) {
test.inc++;
}
}
});
thread.start();
}
while(Thread.activeCount()>1) {
Thread.yield();//如果存在还没有结束的线程,就需要尽量让出CPU供它们运行
}
System.out.println(test.inc);
}
}
- 我们将这段代码运行三次
- 这结果和我们预想的不一样,为什么呢?下次再说
二、源码: