在并发编程中,我们通常要保证可见性、原子性、有序性:
- 可见性:当多个线程共享同一变量时,若其中一个线程对该共享变量进行了修改,那么这个修改对其他线程是立即可见的。
- 原子性:一个或多个操作为一个整体,要么都执行且不会受到任何因素的干扰而中断,要么都不执行。
- 有序性:程序执行的顺序按照代码的先后顺序执行。
但是volatile只保证了可见性和有序性。
java内存模型JMM介绍——可见性
- JVM运行程序的实体是线程,而每个线程创建时由JVM分配各自的工作内存。线程的工作内存是各自私有的数据区域,互相各不影响。
- java内存模型中所有的变量是存储在主内存中的,主内存是线程共享的区域,但是线程对变量的操作(读取赋值)必须在工作内存中进行, 当多个线程访问同一个变量(放在主存中)的时候,并不是直接在主存中操作变量,而是将此对象分别拷贝到每一个线程各自的工作内存中, 当其中一个线程操作了变量之后,需要将修改后的对象(也就是最新值)重新写回主内存,也要将最新值同步给其他的线程。(可见性:让其他的线程可以看到。) 线程之间的通信(传值)必须通过主内存来完成。
volatile
- 一般来说CPU都是有计算核心的, 除了有计算核心外,它还有一块叫高速缓存,(二级缓存), 这个缓存存储的就是 CPU 里正在计算的临时数据, 在计算机里面有一块是内存, 有一块是硬盘(磁盘), 还有一块是cpu 里的缓存 ,把磁盘中的classes 字节码文件读取到内存中, 内存中的一个类对象和对象,在运行是,会把对象总的一些变量读取到缓存中
- 通知OS操作系统底层,在CPU计算过程中,都要检查内存中数据的有效性。保证最新的内存数据被使用。
- volatile的非原子性问题
- volatile, 只能保证可见性,不能保证原子性。
- 不是加锁问题,只是内存数据可见
- 防止指令重排序
- java虚拟机优化内部指令,对象创建初始化
- synchronized
- 防止指令重排序,是在进入代码块后的禁止重排序
多级缓存
- L1 Cache (一级缓存)是CPU第一层高速缓存, 分为数据缓存和指令缓存, 一般服务器CPU的L1缓存的容量通常在32-4096kb
- L2 Cache (二级缓存) 由于L1高速缓存的容量限制, 为了再次提高CPU的运算速度, 在CPU外部放置一高速缓存存储器, 即二级缓存
- L3 Cache(三级缓存)现在都是内置的, 而它的实际作用既是, L3缓存的应用可以进一步降低内存延迟, 同时提升大数据量计算时处理器的性能. 具有较大L3缓存的处理器更有效的文件系统缓存行为及较短消息和处理器队列长度. 一般是多核共享一个L3缓存
- CPU在读取数据时, 先在L1中寻找, 再从L2中寻找, 再从L3中寻找, 然后是内存, 最后是外存储器
// volatile 一个很明显的区别
volatile boolean b = true;
void m() {
System.out.println("start");
while (b) {
}
System.out.println("end");
}
public static void main(String[] args) {
final EuserApplication t = new EuserApplication();
new Thread(new Runnable() {
@Override
public void run() {
t.m();
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t.b = false;
}