JVM的内存模型,也类似于上图,即有主内存,多个线程并发的操作主内存中的数据时,就可能会出现数据不一致的问题
JVM内存模型.png
MESI 协议
也叫缓存一致性协议
状态
说明
M(修改,Modified)
本地处理器已经修改缓存行, 即是脏行, 它的内容与内存中的内容不一样. 并且此 cache 只有本地一个拷贝(专有)。
E(专有,Exclusive)
缓存行内容和内存中的一样, 而且其它处理器都没有这行数据。
S(共享,Shared)
缓存行内容和内存中的一样, 有可能其它处理器也存在此缓存行的拷贝。
I(无效,Invalid)
缓存行失效, 不能使用。
那么为了防止并发问题
我们就需要保证
可见性
即我们的修改,对于其它线程来说是可见的
volatile和final和synchronized都可以保证可见性
被 final 修饰的字段在声明时或者构造器中,一旦初始化完成,那么在其他线程无须同步就能正确看见 final 字段的值。
有序性
有时候为了性能优化,操作系统在保证最终结果不变的情况下,会帮我们进行指令重排,单线程下指令重排是没问题的,但多线程并发的时候就会出现问题
volatile和synchronized都可以
原子性
假如执行a,b,c操作,它们同时成功,或者同时失败,那么我们就说它是一个原子操作
synchronized或者使用CAS,将多个操作放到一个CAS中
备注:volatile 并不能保证原子性
最佳实践:我们可以使用volatile 来修饰boolean类型的变量
除了保证以上三点之外,线程安全还可以通过将数据放入到线程安全的容器中,保证多个线程不会并发的去修改
线程安全容器的基本原理,就是将数据和线程进行绑定,但同时也需要注意Thread是一个长生命周期的对象,而我们使用的数据可能只是在某个时间段使用,如果线程一致不结束,有可能会导致内存泄漏
常见的线程安全容器
ThreadLocal
备注:使用完后,需要调用下remove()或者其它方法,否则可能会导致内存泄漏,调用remove()它会帮你断开那个引用,那么就会被GC回收掉
CopyOnWrite容器
如CopyOnWriteArrayList和CopyOnWriteArraySet
基本原理,就是你写的是副本,读的时候读的是主,所以你修改的副本,是不会影响到读的
redis的fork()函数写rdf和aof用的就是CopyOnWrite原理
网友评论