zoukankan      html  css  js  c++  java
  • 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的执行顺序则不会被调整。

  • 相关阅读:
    行为的封装
    分页功能-----》链表实现
    python入门教程链接
    作用域 属性链接 存储类型
    Codeforces Round #598 (Div. 3)
    CCPC2018-湖南全国邀请赛
    【洛谷P2494】 [SDOI2011]保密(分数规划+最小割)
    【洛谷P3329】 [ZJOI2011]最小割(最小割树)
    【BZOJ3716】[PA2014]Muzeum(贪心+网络流)
    【洛谷P4542】 [ZJOI2011]营救皮卡丘(费用流)
  • 原文地址:https://www.cnblogs.com/woniu4/p/8329634.html
Copyright © 2011-2022 走看看