大家都知道volatile关键字是在多线程学习中非常关键的存在,可以保证一个变量在多线程中是一直可见的,好像刚学习的时候只知道这些,但其中的原理远不止于此。原理其实就是标题里已经说明的读写屏障。
先讲一下volatile关键字的作用,众所周知,volatile是一个类型修饰符,用于修饰变量。它主要用于确保变量的可见性和防止指令重排序。当一个变量被声明为volatile时,多个线程对这个变量进行访问时,每个线程都会读取变量的最新值,而不是使用线程本地缓存中的副本。这就保证了不同线程对该变量的操作的可见性。
那他是怎么实现可见性和防止指令重排序的呢,这两个特性看似根本不相干啊,这是我刚学这个知识点以及学了好几遍之后还是这种看法,到我真正明白这个原理的时候才恍然大悟。先透彻的分析一遍原理,内存屏障也就是读写屏障,分为读屏障和写屏障,解释一下定义:内存屏障(Memory Barrier)是一种硬件或软件层面的机制,用于控制处理器对内存操作的顺序,确保特定的操作按照预期的顺序执行。它主要是为了解决多处理器系统中的内存一致性问题。
简单来说,cpu内缓存和内存的关系就类似于应用层里的redis和DB的关系,在多线程情况下,默认取数据的时候都是从redis(缓存)里取,只有线程结束时才会写回DB(内存),这时,当A线程进行了修改一个变量但线程未结束,B线程如果不停需要根据这个变量计算结果,这是就会出现常见的问题,原理也就是这个:缓存还未写回内存。
利用应用层的思想直接做缓存同步不就行了,这还不太一样,我们开发中可以直接读数据库(内存)也可以直接读缓存,但是在硬件方面只能直接读缓存。所以只能考虑缓存和内存间的同步问题,volatile对这个变量进行了一种封装,当这个变量写入的时候给他加写屏障,读这个变量的时候加读屏障,写屏障简单理解就是把(所有)缓存里的数据直接push到内存中,类似于事务,A事务和B事务谁先开始无所谓,决定执行顺序的是谁先提交,就是这个道理,这个写操作代码先push进去了,代表之前的写操作的代码也一并push进去了,肯定可以保障后面的代码无法重排到写操作之前的代码。读屏障也一个道理,把内存里的(所有)数据给pull到线程缓存中,这样不仅实现了有序性又实现了可见性。
杂谈:这种设计对吗,当然是对的,但是就一定好吗,我感觉对于开发来说也许有失偏颇,不仅让代码变得复杂而且两种无论是相同的特性即使是毫无相关,分开也远比合在一起好得多,有一种偏锋的感觉。