如果内容有错误或者您有不同的见解,请关注我。想要思维导图的小伙伴们记得留言哦。
【问题】为啥出现这三个名词?
因为:现在计算机处理数据可以并发,也就会出现多个线程操作一个数据(代码块)的情况。多核CPU在处理数据的时候会将内存的数据复制到高速缓存中,然后再处理数据,处理之后在写回到主内存中。各个线程都复制了一份数据,又写回内存,内存的数据自然就会有可能变得不一致。比如A线程获取a的值放在自己的内存中,改了一下,还没放回到主内存时,B也读取了a,也操作了。此时A放回去,之后B不知道A刚刚修改了。这三个改变都是基于多线程的,也就说在多线程下会有这三个问题要解决。
原子性(Atomicity)是多线程之间保证只能有一个线程在同一个时间执行。
【释义】一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。
这句话经常见但是你会发现,什么是一个或多个操作,理解起来比较费劲,无非就是读写数据嘛,其实如果说我们在读写一个数据的时候,这个读写实际上是两个操作(读和写),那么原子性就是两个操作一块执行,我读了就是我写,你读了我就不写了或者连读也不读。像java源码中:AtomicInteger能保证int这个值的原子性,而synchronize{int a,String b}能保证整个代码块的原子性。
可见性(Visibility)是多线程之间保证变量可以相互读写
【释义】当一个线程修改了变量的值,其他线程能够立即获取到修改之后的值。就是说线程A修改了一个变量之后,线程B马上读取应该就能拿到最新的值。怎么保证这个可见性呢?使用volatile、synchronized、lock、atomicXXX类来保证。
【volatile】
线程在读取数据的时候,不是从缓存中读取了,而是直接从内存(共享内存)中读取这个值。也就是说被volatile修饰的变量在内存中所有的线程在使用时是先读,都不在去缓存一份副本。
【注意】
volatile只能保证可见性,不能保证原子性。读取块,但是写的时候慢(需要执行内存屏障指令,防止指令重排序。
【synchronized】
这货也可以保证可见性,在加锁时清空工作内存的共享变量的值,从而从内存中重新读取共享变量的值。在解锁时需要将工作内存中的共享变量的值写入到主内存。来保证主内存中的数据都是最新的,同时也保证了可见性。
【Lock】
这货为啥能保证可见性呢?因为该类继承了AbstractQueuedSynchronizer,这个类中使用的是state这个变量,而这个变量是volatile 修饰的。
/**
* The synchronization state.
*/
private volatile int state;
【指令重排序】
指令重排序不会破坏依赖关系,它会保证执行结果总是一样。指令重排序是在多线程执行程序时为了提高CPU使用率才会去优化和重排序指令(代码)。
我们在使用volatile修饰的变量的时候,这个代码执行到此处,会先去主内存(共享内存)中读取数据,然后在继续操作,同理synchronized也会防止指令重排序。
为什能防止指令重排序呢?
我觉得就是因为我们在读取volatile修饰的变量的时候是从主内存读取的,而不是自己的线程副本中,在使用该变量时CPU不知道当前值的内容或者已被通知该值已经改变,所以不能重排序。
有序性(orderly)是多线程之间在在执行同一个代码的时候有顺序
【释义】程序按照先后顺序执行,就是说代码在多个cpu上执行,要按照一定顺序执行,A线程执行到此处,其他的线程就得等着A执行完才能执行,就是让代码有顺序的执行。别瞎搞。
【理解】CPU重排序其实是最大利用CPU的资源,不能让CPU闲着,但是如果你让CPU能按照顺序执行,CPU的资源肯定会浪费,因为没有拿到锁的线程都睡着呐。
synchronized保证了有序性(根据as-if-serial语义(就好像是顺序执行一样),无论编译器和处理器怎么优化也好,重排序指令也好,单线程结果一定是正确的。)有了这个sync整个世界都是顺序的,乖的很。
其他说明:
可见性:缓存一致性协议
当CPU对变量进行写操作时发现,变量是共享变量,那么就会通知其他CPU中将该变量的缓存行设置为无效状态。当其他CPU在操作变量时发现此变量在的缓存行已经被设置成了无效,那么就会去主内存中重新读取最新的变量。
内存屏障:一组CPU指令,用于实现对内存操作的顺序限制。
Java编译器,会在生成指令系列时,在适当的位置会插入内存屏障来禁止处理器对指令的重新排序。
如果内容有错误或者您有不同的见解,请关注我。想要思维导图的小伙伴们记得留言哦。