java内存模型
java内存模型(JMM)是一种规范,定义了程序中变量的访问规则,目的是解决由于多线程通过共享内存进行通信时,由工作内存数据不一致、编译器指令重排序、处理器优化等带来的原子性、有序性和缓存一致性等问题。
在多核CPU的环境下,多线程分别在不同的核心上执行,当多个线程访问进程中的某个共享内存时,每个核心都会在各自的CPU缓存中保留一份共享内存的拷贝,由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自cache之间的数据就有可能不同。
Java内存模型分为主内存和工作内存,同时模型规定了所有的变量(共享变量)都存储在主内存中,每条线程有自己的工作内存,线程的工作内存中保存的变量是主内存中的变量的一个副本,线程对变量的所有操作都必须在自己的工作内存中进行,不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
java内存模型要解决的就是工作内存与主内存之间的数据同步问题。java中的volatile、synchronized、final以及concurren包等就是解决这个问题的,
java中的synchronized关键字通过monitorenter和monitorexit两个字节码指令实现加锁解锁操作,保证同一时刻只有一个线程能获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中,从而保证原子性、可见性和有序性。
volatile关键字可以保证变量的可见性 和 禁止指令重排序优化。被volatile修饰的变量在被修改后会立即同步到主内存,并将其他线程中的这个变量副本置失效,其他线程在访问这个变量之前都从主内存中刷新。同时,对应volatile变量,Java内存模型会在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令,内存屏障能够阻止屏障两侧的指令重排序,从而保证了程序的有序性,另外内存屏障还会强制更新一次不同CPU的缓存。
ps:volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,但需要特别注意, volatile不能保证复合操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。
对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。
如果使用volatile修饰long和double,那么其读写都是原子操作
对于64位的引用地址的读写,都是原子操作
volatile和synchronized的区别
1.volatile仅能使用在变量级别; synchronized则可以使用在变量、方法、和类级别的2.volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性3.volatile不会造成线程的阻塞; synchronized可能会造成线程的阻塞。4.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
synchronized与lock的区别
- synchronized是java中的关键字,是基于jvm实现的,属于内置锁,java中的每一个对象都可以作为锁;lock是接口,是基于语言层面实现的的锁。
- synchronized在发生异常时,会自动释放线程占有的锁,不会发生死锁;而lock在发生异常时,如果没有主动调用unLock()来释放锁就有可能造成死锁,因此在使用Lock时需要在finally块中释放锁。
- Lock可以让等待说的线程响应中断,而synchronized不能。
- Lock可以判断是否成功获取锁,而synchronized不能。
- lock可以是公平的,也可是非公平的,而synchronized只能是非公平的。
- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”。