1,CAS是一种处理器指令,能够将read-modify-write和check-and-act之类的操作转换为原子性,但是不能保证可见性。
2,Java中对象实例化a b=new a();在内存中的操作:首先分配对象所需的存储空间objRef=allocate(a.class);其次初始化引用对象,是一个默认值,而不是初始值invokeConstructor(objRef),最后将对象引用写入共享变量b=objRef。由于内存指令重排序,次序变为132,导致这个实例不为null,而是引用实例,而这个引用实例未初始化完成,导致程序报错。
3,static关键字可以保证线程读取到相应字段的初始值,因为虚拟机使用一种懒加载机制,没有访问的时候是默认值为null,false,只有访问变量的时候才返回初始值,具有初始值可见性,但是假如被其他线程修改了,就不具有可见性了。
4,final关键字也能保证字段的初始值,即初始化完毕的,保证代码执行的有序性,禁止处理器重排序a.y=0必须在a=objRef之前,不能排在最后,但不能保证变量的可见性。
5,存储子系统(写缓冲器)导致内存重排序,编译器、运行时和处理器导致指令重排序。存在数据依赖关系不会重排序,存在控制依赖关系会重排序,重排序会导致线程安全问题。
6,屏障可见性分类:加载屏障LoadLoad,存储屏障StoreLoad,有序性分类:获得屏障(LoadLoad和LoadStore的组合)和释放屏障(LoadStore和StoreStore的组合)。
7,处理器中的高速缓存是一种存储速率比主内存快,容量比主内存小的存储部件,有一级缓存,二级缓存,三级缓存,每个处理器都有高速缓存,这就涉及到缓存一致性,目前广泛使用的是MESI协议。但是这个一致性有一个弱点就是处理器执行写内存操作时,必须等待其他所有处理器将其高速缓存中的相应副本数据删除并接受到这些处理器所回复的Invalid Response/Read Response消息之后才能将数据写入高速缓存,造成写操作的延迟,所以引入写缓冲器和无效化队列。
8,处理器中的写缓冲器是处理器内部的容量比高速缓存还小的私有的高速存储组件,它有若干条目Entry,它无法读取其他处理器上的写缓冲器的内容,它不等待Invalid Acknowledge消息,使得写操作的执行处理器在其他处理器回复Invalid Response/Read Response消息的时候能够执行其他指令,提高执行效率。无效化队列使得处理器接受到Invalid消息之后不删除指定地址对应的副本数据,直接存入无效化队列,之后回复Invalid Acknowledge消息,减少写操作所需的等待时间。
9,存储转发表示处理器在执行读操作的时候会根据内存地址查询写缓冲期,如果没有,则再从高速缓存读取数据,如果有,则直接返回。但是存储转发技术也可能导致可见性问题。假设处理器 Processor0在t1时刻更新了某共享变随后又在t2时刻读取了该变量。在 t1时刻到t2时刻之间的这段时间内其他处理器可能已经更新了该共享变量,并且这个更新的结果已经到达该处理器的高速缓存中。但是Processor0在t1时刻所做的更新仍然停留在该处理器的写缓冲器之中,那么存储转发会使Processor0直接从其写缓冲器读取该共享变量的值。也就是读取的是一个旧值,并不从高速缓存中读取值,使得其他处理器更新的值无效。考虑到存储转发技术的这个副作用,从读线程的角度来为了使读线程能够将其他线程对共享变量所做的更新同步到该处理器的高速缓存中,需要清空该处理器上的写缓冲器和无效化队列。
10,写缓冲器导致内存重排序和可见性,就是修改的变量还在写缓冲器里面,其他读取不到,这时可以冲刷写缓冲器,即将写缓冲器里面的内容写入高速缓存,但是处理器本身无法保证冲刷对程序来的及时,所以需要编译器底层系统借助内存屏障中的存储屏障(StoreBarrier)使执行该指令的处理器冲刷其写缓冲器,把数据送到高速缓存中,供其他线程读取。
11,无效化队列也会导致变量可见性,因为处理器在执行内存读操作前没有根据无效化队列中的内容删除高速缓存中副本数据,导致读处理器读取的数据是旧的,这时需要内存屏障中的加载屏障(Load Barrier),它会高速缓存中的无效化的内容中的缓存条目的状态都标记为I,从而使得处理器后续执行读操作时必须发送Read消息,以将其他处理器对共享变量所做的更新同步到该处理器的高速缓存中。