java并发理论摘要

  • 数据依赖
    所谓的数据依赖是指,如果多个java指令之间顺序变化会影响结果,那么说明这个这两个指令存在数据依赖关系。

  • as-if-serial
    在一个线程中,会考虑数据依赖问题,也就是说如果两个指令有数据依赖的关系,那么不会重排序,因为会影响执行结果。但是在多线程中,是不考虑两个指令的数据依赖关系的,否则会严重影响多处理器的并行效率。as-if-serial是针对单线程来说的。

  • 多线程重排序会影响语义
    多线程(使用共享变量)条件下,重排序就会影响语义。比如控制依赖(if),满足if条件才会执行里面的操作,里面的操作是使用共享变量做运算,为了提高指令的并发,会先做运算把结果保存在缓存在,等if真的满足才赋值,但是这个共享变量因为是共享的,必然这个值是不固定的,所以执行结果会不相同,破坏了语义。

  • 顺序一致性内存模型
    顺序一致性内存模型其实是另外一种内存模型,这种内存模型和JVM的内存模型是截然不同的,顺序一致性这种内存模型在多线程的情况下,是天然没有线程之间可见性问题的,顺序一致性内存模型中,所有线程都是直接面向主内存的,线程没有自己的缓存内存。
    1.顺序一致性内存模型可以保证单线程内会按照程序顺序执行(不重排)
    2.顺序一致性内存模型中,线程的操作都是面对主内存的,保证所有线程看到执行顺序是一致(内存可见性)
    3.顺序一致性内存模型中,能保证所有的读写操作都具有原子性(读写原子性)

  • volatile内存语义
    volatile的语义就是来解决JVM内存模型中的三个问题的
    1.JVM内存模型中已经通过as-if-serial解决了重排序在单线程内执行结果不变的问题,但是volatile变量读写会限制程序上下文的重排序,通过内存屏障
    2.JVM内存模型中用 volatile修饰的变量会特殊处理,每当碰到读写 volatile修饰的变量都会唤醒线程内存和主内存的数据交互,保证了volatile修饰的变量具备内存可见性;同时volatile通过限制程序上下文的重排序,通过内存屏障达到程序上下文相关变量的内存可见性
    3.JVM内存模型中用 volatile修饰的变量会特殊处理,会保证变量读写事务的原子性

  • 为什么需要volatile
    其实我觉得volatile对于提高程序性能是有很大帮助的,volatile其实就像一个开关,你需要满足你的程序具有非重排序内存可见性和读写事务的原子性,那么你就加上这个关键字修饰,不需要就不要加。顺序一致内存模型就是默认所有操作都满足非重排序可见性和原子性,但是这样会很影响指令的并发性,而JVM内存模型就不一样,需要和主内存中交互的就加上volatile,否则一些数据的状态在线程的缓存中就可以了,这些数据对主内存或者其他线程的意义也不大

  • 锁的内存语义
    常见的锁主要是两种,分别是使用synchronized关键字的同步方法同步块和使用Lock接口的实现锁,这两种锁存在相同的语义
    临界区代码互斥:同步方法内的代码或者lock和unLock方法直接的代码是同步互斥的
    临界区代码保证可见性:同步方法内的代码或者lock和unLock方法的代码里面,在获取锁的时候会强制到主内存中读取数据,在释放锁时强制将数据从线程缓存写到主内存中

  • synchronized和Lock和CAS锁内存语义实现方式
    Lock锁:
    1.使用AQS实现了线程的挂起,满足了锁的代码互斥
    2.使用了volatile变量status,获取锁读主内存释放锁写主内存,防止了重排序,并且使用上内存屏障和volatile保证可见性。
    3.使用本地CAS方法,CAS内部保证了原子操作,volatile变量status(虽然只是32位,不存在原子问题)也保证了原子读写事务
    4.本地CAS方法其实是一个封装好的一个具有锁语义的指令,CAS内部实现了锁,也满足代码互斥,非重排,可见性,原子性
    5.就原子性来说,Java实现的原子操作是建立在c++指令的原子(Lock),CAS实现的原子是c++依赖的底层的原子(Lock Cmpxchg), 所以java直接使用CAS就能保证原子性了,原子性的目的主要是两个指令必须同时生效,否则拿到的数据就是错误的数据。
    CAS锁:
    1.内部使用Lock Cmpxchg指令满足代码的互斥
    2.Lock Cmpxchg禁止重排序
    3.Lock Cmpxchg指令确保执行完将数据刷到主内存,满足可见性
    4…Lock Cmpxchg指令使用缓存锁定来保证指令执行的原子性
    synchronized锁:
    内部使用monitorenter和monitorexit字节码来实现的,可以保证原子性和内存的可见性也会考虑禁止重排

  • 总结
    其实总而言之,JVM内存模型虽然对比顺序一致模型并发效率提高了,但是带来了可见性,原子性,重排序的问题。那么提高并发的同时有时候也是需要解决可见性,原子性,重排序。
    1.可见性问题:使用volatile关键字修饰,被迫读取写入主内存来解决
    2.原子性:使用volatile关键字修饰的64位变量会满足,使用CAS满足多个指令原子性
    3.重排序:使用volatile关键字修饰的变量的读写规则,在大多数情况下会使用内存屏障来禁止重排序

猜你喜欢

转载自blog.csdn.net/weixin_38312719/article/details/105415770