在并发编程中都避不开原子性,可见性,有序性问题。这三个问题都是人们抽象出来,而实际上对应的就是缓存一致性,处理器优化,指令重排序问题。
原子性: 就是一个操作中CPU不可以在中途展厅然后再调度,即不被中断操作,要不执行完成,要不就不执行
可见性:就是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值
有序性,就是程序执行的顺序按照代码的先后顺序执行。
缓存一致性问题就是可见性问题,处理器优化时可以导致原子性问题,指令重排会导致有序性问题。
什么是内存模型?
为了保证共享内存的正确性(可见性,原子性,有序性),内存模型定义了共享内存系统走红多线程程序读写操作的行为的规范。通过这些柜则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关,与并发有关,与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
内存模型解决并发问题主要就是采用两种形式,限制处理器优化和内存屏障。
什么是Java内存模型?
Java程序是需要运行在java虚拟机上的,JAVA内存模型JMM就是一种符合内存规范的,屏蔽了各种硬件和操作系统搞得访问茶席,保证了java程序再各个平台下对内存的访问都能保证效果一致的机制以及规范。
java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝。线程对变量的所有操作都是在自己的工作内存中操作的,而不能直接读写主内存,不同的线程间也不能直接通信,线程间的传递都需要自己的工作内存和主存之间进行数据同步进行。
而JMM就作用于工作内存和竹村之间数据同步过程,他规定了如何做数据同步以及什么时候做数据同步
所以,再来总结下,JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
java内存模型的实现
在java中提供了一系列和并发处理相关的关键字,比如volatile synchonized final 还有juc包
在开发多线程的代码的时候,我们可以直接使用synchonized等关键字来控制并发,从来就不需要关心底层的编译器优化,缓存一致性等问题,所以JAVA内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者使用。
并发编程要解决原子性、有序性和一致性的问题,我们就再来看下,在Java中,分别使用什么方式来保证。
原子性
在java中,为了保证原子性,提供了亮哥高级的字节码指令monitorenter和monitorexit。在java中对应的关键字就是synchonized。
可见性
java内存模型是哦通过在变量修改后将新值同步回主内存,在变量读取前冲主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。
java中的volatile关键字提供了一个功能,就是被其修饰的变量在被修改后可以立即同步到主内存中,被其修饰的变量在每次使用之前都是从主内存刷新的,因此可以使用volatile来保证多线程操作时后的变量的可见性。
有序性
在java中可以通过synchronized和volatile来保证多线程之间操作的有序性,实现的方式不一样。
volatile关键字会禁止指令重排,synchronized关键字保证了同一时刻只有一条线程操作,
好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了,好像synchronized
关键字是万能的,他可以同时满足以上三种特性,这其实也是很多人滥用synchronized
的原因。
但是synchronized
是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。