volatile关键字解析

Posted woniu4

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volatile关键字解析相关的知识,希望对你有一定的参考价值。

volatile详解
接下来,我们详细讲述一下volatile关键字
volatile关键字具有两重语义

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。
  • 禁止指令重排序

依然使用上文RunThread案例说明,上文中已经提到了,由于主线程修改了isRunning的值后,RunThread线程并没有看到其值的变化,因此,进入了死循环,但是当isRunning加上volatile关键字后,效果就不一样了。

  1. 使用volatile关键字会强制将修改的值立即写入主存;
  2. 使用volatile关键字的话,当主线程修改时,会导致RunThread的工作内存中isRunning变量的缓存值变得无效。
  3. 由于RunThread的工作内存中缓存变量isRunning缓存无效,所以会再次从主存中读取isRunning变量值。

这样就保证了RunThread可以顺利结束。


关于原子性,我们先看个例子:

public class TestCount
{
    private volatile int n;
    
    public void increase()
    {
        n ++;
    }
    
    public static void main(String[] args)
    {
        TestCount count = new TestCount();
        for(int i=0; i<10; i++)
        {
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        count.increase();
                };
            }.start();
        }
        
        if(Thread.activeCount() > 1)
        {
            Thread.yield();
        }
        System.out.println(count.n);
    }
}

关于这个结果,应该很多人会认为是10000吧。但是实际上,运行结果小于10000,这是由于n++操作并非原子性操作,其涉及三个操作

  1. 读出n的旧知
  2. 对n+1
  3. 写入新值

虽然volatile可以保证每个线程读取到的是最新的内存值,但是,如果10个线程第一步都是正确的,第二步和第三步单线程看的话也都是没有问题的,但是如果并发的话,就会导致同一个值被多次写入了内存。
可见volatile并不能保证原子性操作。
对于这一类问题,我们可以通过synchronized、lock或者原子操作类经行操作。
synchronized版本:

public class TestCount
{
    public  int inc = 0;
    
    public synchronized void increase() {
        inc++;
    }
 
    public static void main(String[] args) {
        final TestCount test = new TestCount();
        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版本:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestCount
{
    public  int inc = 0;
    Lock lock = new ReentrantLock();
 
    public  void increase() {
        lock.lock();
        try {
            inc++;
        } finally{
            lock.unlock();
        }
    }
 
    public static void main(String[] args) {
        final TestCount test = new TestCount();
        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);
    }
}

原子操作类版本:

import java.util.concurrent.atomic.AtomicInteger;

public class TestCount
{
    public  AtomicInteger inc = new AtomicInteger();
    
    public  void increase() {
        inc.getAndIncrement();
    }
 
    public static void main(String[] args) {
        final TestCount test = new TestCount();
        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);
    }
}

对于有序性,我们可以看下下面的这个例子:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
     
//线程2:
while(!inited ){
    sleep()
}
doSomethingwithconfig(context);

如果没有对inited加volatile关键字,则可能会对语句1和语句2进行调整,因此两者并无依赖关系,并且单线程下,也并不会影响结果。但是两个线程下,可能由于context并没有初始化完成,导致意想不到的结果。
如果对inited加上volatile,这个问题就不会发生了。语句1和语句2的执行顺序则不会被调整。

以上是关于volatile关键字解析的主要内容,如果未能解决你的问题,请参考以下文章

volatile原理解析

volatile关键字解析

深入解析volatile关键字

深入解析volatile关键字

volatile关键字解析

volatile关键字解析