shrunk是什么意思 assign是什么意思( 二 )


store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用 。
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中 。
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 。
我再补充一下JMM对8种内存交互操作制定的规则吧:
不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write 。
不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存 。
不允许线程将没有assign的数据从工作内存同步到主内存 。
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量 。就是对变量实施use、store操作之前,必须经过load和assign操作 。
一个变量同一时间只能有一个线程对其进行lock操作 。多次lock之后,必须执行相同次数unlock才可以解锁 。
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值 。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值 。
如果一个变量没有被lock,就不能对其进行unlock操作 。也不能unlock一个被其他线程锁住的变量 。
一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存 。
面试官:讲一下volatile关键字吧
内心:这可是重头戏呀,可不能出岔子~
很多并发编程都使用了volatile关键字,主要的作用包括两点:
保证线程间变量的可见性 。
禁止CPU进行指令重排序 。
可见性
volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的 。普通变量则需要重新读取才能获得最新值 。
volatile保证可见性的流程大概就是这个一个过程:

shrunk是什么意思 assign是什么意思

文章插图
volatile一定能保证线程安全吗
先说结论吧,volatile不能一定能保证线程安全 。
怎么证明呢,我们看下面一段代码的运行结果就知道了:
/*** @author Ye Hongzhi 公众号:java技术爱好者**/ public class VolatileTest extends Thread { private static volatile int count = 0; public static void main(String[] args) throws Exception { Vector<Thread> threads = new Vector<>(); for (int i = 0; i < 100; i++) { VolatileTest thread = new VolatileTest(); threads.add(thread); thread.start(); } //等待子线程全部完成 for (Thread thread : threads) { thread.join(); } //输出结果,正确结果应该是1000,实际却是984 System.out.println(count);//984 } @Override public void run() { for (int i = 0; i < 10; i++) { try { //休眠500毫秒 Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } count++; } } }为什么volatile不能保证线程安全?
很简单呀,可见性不能保证操作的原子性,前面说过了count++不是原子性操作,会当做三步,先读取count的值,然后+1,最后赋值回去count变量 。需要保证线程安全的话,需要使用synchronized关键字或者lock锁,给count++这段代码上锁:
private static synchronized void add() { count++; }禁止指令重排序
首先要讲一下as-if-serial语义,不管怎么重排序,(单线程)程序的执行结果不能被改变 。
为了使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率,只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序 。
重排序的种类分为三种,分别是:编译器重排序,指令级并行的重排序,内存系统重排序 。整个过程如下所示:
shrunk是什么意思 assign是什么意思

文章插图
指令重排序在单线程是没有问题的,不会影响执行结果,而且还提高了性能 。但是在多线程的环境下就不能保证一定不会影响执行结果了 。
所以在多线程环境下,就需要禁止指令重排序 。
volatile关键字禁止指令重排序有两层意思:
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行 。
在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行 。
下面举个例子:
private static int a;//非volatile修饰变量 private static int b;//非volatile修饰变量 private static volatile int k;//volatile修饰变量 private void hello() { a = 1;//语句1 b = 2;//语句2 k = 3;//语句3 a = 4;//语句4 b = 5;//语句5 //以下省略... }