那么线程1读取到的就是最新的正确的值 。
② volatile保证原子性吗?
从上面知道volatile关键字保证了操作的可见性,但是volatile能保证对变量的操作是原子性吗?
下面看一个例子:
public class Test {public volatile int inc = 0;public void increase() {inc++;}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1)//保证前面的线程都执行完Thread.yield();System.out.println(test.inc);}}
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000 。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字 。
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000 。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性 。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性 。
在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存 。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
假如某个时刻变量inc的值为10,执行顺序是如下:
1)线程1读取inc的值后,还没有操作就被阻塞了 。
2)线程2被唤醒,从主存读取inc的值,加1,然后被阻塞 。(此时还没来得及把新的值重新赋值给inc,当然也还没同步到主存)
3)线程1被唤醒,inc值加1,然后同步到主存(线程1结束)
4)线程2被唤醒,把最新的值赋值给inc,同步到主存(此时线程2,inc的值在第2步时已经被处理过了,仅仅只是把新的值赋值给inc而已 。这个时候是不会再去读取inc的缓存行的,虽然inc的缓存行此时已经无效了)
根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的 。
把上面的代码改成以下任何一种都可以达到效果:
采用synchronized:
public class Test {publicint inc = 0;public synchronized void increase() {inc++;}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1)//保证前面的线程都执行完Thread.yield();System.out.println(test.inc);}}
采用Lock:
public class Test {publicint inc = 0;Lock lock = new ReentrantLock();publicvoid increase() {lock.lock();try {inc++;} finally{lock.unlock();}}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1)//保证前面的线程都执行完Thread.yield();System.out.println(test.inc);}}
采用AtomicInteger:
public class Test {publicAtomicInteger inc = new AtomicInteger();publicvoid increase() {inc.getAndIncrement();}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1)//保证前面的线程都执行完Thread.yield();System.out.println(test.inc);}}
在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类,即对基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数)进行了封装,保证这些操作是原子性操作 。atomic是利用CAS来实现原子性操作的(Compare And Swap),CAS实际上是利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作 。
③ volatile能保证有序性吗?
在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性 。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行 。
可能上面说的比较绕,举个简单的例子:
推荐阅读
- Java正则表达式详解
- Jackson注解详解
- 华硕主板后缀字母代表什么含义?华硕主板型号命名规则与系列详解
- 决明子菊花茶详解,决明子菊花茶的做法
- Apollo配置中心管理后台的详解
- 开发者手撸类谷歌搜索关键字智能匹配功能系统
- 教你紫砂壶的保养方法,详解紫砂壶冲泡普洱茶的方法
- 一文详解,PHP 协程:Go + Chan + Defer
- Serverless无服务器架构详解
- Docker---Dockerfile 详解