锁在保证可见性的时候,会有两个操作刷新处理器缓存(获得锁)和冲刷处理器缓存(释放锁)。前一个动作保证了:当前线程能够读取到前一个线程对共享数据的更新,后一个动作保证了:该锁的所有持有线程对这些数据所做的更新对该锁的所有持有线程可见。而这两个动作的实现是通过:内存屏障完成的。
什么是内存屏障?
内存屏障是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。其作用是:禁止编译器、处理器重排序从而保证有序性。他在指令序列中就像是一堵墙使其两端的指令无法“穿越”,一旦穿越了就重排序了。
内存屏障的分类:
①从可见性保证划分:
加载屏障:用于刷新处理器缓存。
存储屏障:用于冲刷树处理器缓存。
java虚拟就会在释放锁对应的指令后插入一个存储屏障,这就保证了写线程在释放锁之前在临界区中对共享变量所做的更新可以冲刷到处理器缓存中,这样下一个线程就可以读到上一个线程的更新操作。
java虚拟机会在申请锁对应的机器码指令后插入一个加载屏障,这使得该线程的执行处理能够将写线程对共享变量所做的更新从其他处理器同步到该处理器的高速缓存中。因此,可见性的保证是通过写线程和读线程成对使用存储屏障和加载屏障实现的。
②按照有序性保障划分:
1》获取屏障:是在一个读操作之后插入该内存屏障。
作用是:禁止该读操作与其后的任何读操作之间进行重排序,这就相当于在进行后续操作之前先要获得相应共享数据的所有权。
2》释放屏障:是在一个写操作之前插入该内存屏障。
作用是:禁止该写操作与其前面的任何读写操作之间进行重排序。这相当于在对相应共享数据操作结束后释放所有权。
总结:
锁对有序性的保证是通过写线程和读线程配对使用释放屏障和加载屏障实现的。
获取屏障禁止了临界区中的任何读、写操作被重排序到临界区之前的可能性,而释放屏障又禁止了临界区中的任何读、写操作重排序倒到临界区之后的可能性。所以临界区中的任何操作都无法重排序到临界区之外(在锁的排他性的作用下,这使得临界区中执行的操作序列具有原子性)。因此写线程在临界区中对各个共享变量所做的跟新会同时对读线程可见。