volatile 关键字(详解)

版权声明:杨杨杨~~的版权 https://blog.csdn.net/weixin_38316697/article/details/85214196

volatile 关键字

说明volatile 关键字之前,首先先简单介绍下java内存模型,因为后续的介绍与java的内存模型息息相关。

详细的就不说明了,百度上都有,简单的说下。

Java 内存模型中的可见性原子性有序性

可见性: 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。

原子性:   原子是世界上的最小单位,具有不可分割性。

有序性: 即程序执行的顺序按照代码的先后顺序执行。


volatile,从字面上说是易变的、不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,

只要是被此关键字修饰的变量都是易变的、不稳定的

volatile关键字的两个特点:

(1)、内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。

(2)、可以禁止指令重排序

首先说下第一个volatile特点,假如不加这个关键字会怎么样,如:

public class test{
    private int key;
    public int getKey(){
        return key;
    }
    public void setKey(int key){
        this.key= key;
    }
}

在这个代码中,test不是线程安全的,因为key的set/get方法都是在没有同步的线程中执行的,假如说有两个线程同步执行,

线程1执行了set方法,线程2的get方法有可能取到值也有可能取不到值,解决办法,就是在成员变量那块加上volatile关键字,这样的话,利用volatile关键字第一个特性,当线程1执行了set方法的时候,线程2的get方法也可以取到值。

以下有个列子,

说明了volatile保证不了原子性这个特点:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
       /**
        *   1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
       *     2)禁止进行指令重排序。
        * **/
    public  volatile int inc = 0;  //没有达到预期原因是volatile关键字保证了java内存模型中的可读性,但递增操作保证不了原子性
    public   void increaseVolatile() {
        inc++;
    }      
    public static void main(String[] args) {
        final test4 test = new test4();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++){
                        test.increaseVolatile();
                    }
                };
            }.start();
        }
        
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println("volatile------"+test.inc);
               /**
         * 结论:
         * volatile关键字可以保证目标的可读性,但是保证不了目标的原子性
         *
         * **/     
     }
}

通过上面的这段代码可以猜出,正常来说它的值应该等于10000,但是运行出的结果却是可能等于10000,也有可能小于10000

如何解决,可以采用synchronized关键字达到预期目的,或者采用Lock达到预期目的,再或者采用AtomicInteger达到预期目的,下面是个对比的列子可以看成可视化的结果:


import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
       /**
        *   1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
       *     2)禁止进行指令重排序。
        * **/
    public  volatile int inc = 0;  //没有达到预期原因是volatile关键字保证了java内存模型中的可读性,但递增操作保证不了原子性
    public  int incc = 0;  //采用synchronized关键字达到预期目的
    public  int inccc = 0;  //采用Lock达到预期目的
    Lock lock = new ReentrantLock();
    public  AtomicInteger incccc = new AtomicInteger(); //采用AtomicInteger达到预期目的
    public synchronized  static  void increaseSynchronized() { //这里包含相关类锁和对象锁的区别的知识,当有多个线程共同应                                                               //用的时候,使用类锁。
        incc++;
    }
    public   void increaseVolatile() {
        inc++;
    }
    public   void increaseLock() {
        lock.lock();
         try { 
             inccc++;
         } finally{
             lock.unlock();
         }
    }
    public   void increaseAtomicInteger() {
        incccc.getAndIncrement();
    }       
    public static void main(String[] args) {
        final test4 test = new test4();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++){
                        test.increaseSynchronized();
                        test.increaseVolatile();
                        test.increaseLock();
                        test.increaseAtomicInteger();
                    }
                };
            }.start();
        }
        
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println("volatile------"+test.inc);
        System.out.println("synchronized-------"+test.incc);
        System.out.println("Lock-------"+test.inccc);
        System.out.println("AtomicInteger--------"+test.incccc);
        /**
         * 结论:
         * volatile关键字可以保证目标的可读性,但是保证不了目标的原子性
         *
         * **/            
    }
}

效果:


volatile关键字的第二个特性应用场景:

1.状态标记量

     /**
     * volatile关键字的应用场景(利用它的有序列性)
     * 1.状态标记量
     * 2.double check 双重检查
     * **/
    volatile boolean flag = false;
    public void volatileYy(){
       while(!flag){
          //.....
       }
    }
    public void setFlag() {
        flag = true;
    }

 2.double check 双重检查

猜你喜欢

转载自blog.csdn.net/weixin_38316697/article/details/85214196