美文网首页面试精选
volatile关键字——最轻量的同步

volatile关键字——最轻量的同步

作者: 奔跑吧李博 | 来源:发表于2021-03-18 23:57 被阅读0次

    我们都知道,现在不管是手机还是电脑,动不动就声称是多核的,多核就是多CPU的意思。因为一个CPU在同一时间其实只能处理一个任务,即使我们开了多个线程,对于CPU而言,它只能先处理这个线程中的一些任务,然后暂停下来转去处理另外一个线程中的任务,以此交替。而多CPU的话,则可以允许在同一时间处理多个任务,这样效率当然就更高了。

    随着CPU读取速度越来越快,就不再是每次去从内存中读取数据,CPU厂商引入了高速缓存功能。内存里存储的数据,CPU高速缓存里也可以存一份,这样当频繁需要去访问某个数据时就不需要重复从内存中去获取了,CPU高速缓存里有,那么直接拿缓存中的数据即可,这样就可以大大提升CPU的工作效率。

    场景举例:

    有两个线程,分别通过两个CPU来执行程序,但它们是共享同一个内存的。现在CPU1从内存中读取数据A,并写入高速缓存,CPU2也从内存中读取数据A,并写入高速缓存。仅读取的情况下是没有问题的。

    但是如果线程2修改了数据A的值,首先CPU2会更新高速缓存中A的值,然后再将它写回到内存当中。这个时候,线程1再访问数据A,CPU1发现高速缓存当中有A的值啊,那么直接返回缓存中的值不就行了。此时线程1和线程2访问同一个数据A,得到的值却不一样了。

    而此时volatile就来出面解决这个问题了。

    可见性

    Java提供了volatile关键字来保证可见性。

    当一个变量被声明成volatile之后,任何一个线程对它进行修改,都会让所有其他CPU高速缓存中的值过期,这样其他线程就必须去内存中重新获取最新的值,也就解决了可见性的问题。

    而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    示例:

    public class VolatileCase {
        private static boolean ready;
    //    private volatile static boolean ready;
        private static int number;
        
        private static class PrintThread extends Thread{
            @Override
            public void run() {
                System.out.println("PrintThread is running.......");
                while(!ready);//无限循环
                System.out.println("number = "+number);
            }
        }
    
        public static void main(String[] args) {
            new PrintThread().start();
            SleepTools.second(1);
            number = 51;
            ready = true;
            SleepTools.second(5);
            System.out.println("main is ended!");
        }
    }
    

    该示例中,在使用valotile声明ready之前。开启子线程,在主线程改变ready值,但是while的无限循环不会停下来,子线程中得不到在主线程中被修改的值。加了volatile修饰后,主线程修改了之后,子线程使用就能立马获取到ready的真实值了,这就是valotile的可见性。

    有序性

    在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

    原子性

    volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性。volatile可以修饰变量,用于多线程的访问,但是在多核的情况下,多线程并发还是存在问题。

    原子性验证示例:

    public class NotSafe {
        private volatile long count =0;
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
        //count进行累加
        public void incCount(){
            count++;
        }
    
        //线程
        private static class Count extends Thread{
    
            private NotSafe simplOper;
    
            public Count(NotSafe simplOper) {
                this.simplOper = simplOper;
            }
    
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    simplOper.incCount();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            NotSafe simplOper = new NotSafe();
            //启动两个线程
            Count count1 = new Count(simplOper);
            Count count2 = new Count(simplOper);
            count1.start();
            count2.start();
            Thread.sleep(50);
            System.out.println(simplOper.count);//20000?
        }
    }
    

    用volatile对count修饰,两个线程同时对count累加,其结果却是每次基本都不一样。

    那么总结来了

    volatile关键字适用于什么样的场景了?适用于一写多读的场景,即只有一个线程来修改值,其他线程来读取值。而多个线程都会写,那么就得用上sychronized关键字了。

    参考:

    volatile关键字在Android中到底有什么用?

    相关文章

      网友评论

        本文标题:volatile关键字——最轻量的同步

        本文链接:https://www.haomeiwen.com/subject/ttnrcltx.html