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的执行顺序则不会被调整。

  • 相关阅读:
    wget 命令用法详解
    VI编辑器的使用方法
    Android APK反编译就这么简单 详解(附图)
    Nginx与X-Sendfile
    腾讯微博OAuth2.0认证介绍
    haporoxy的keeplaive ZZ
    如何监听非本地IP
    haproxy配置直接重定向url
    LVS与其他负载均衡软件的区别
    J2EE基础总结(4)——JSP
  • 原文地址:https://www.cnblogs.com/woniu4/p/8329634.html
Copyright © 2011-2022 走看看