本文共 2183 字,大约阅读时间需要 7 分钟。
为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。
synchronized同步块:
在多线程访问的时候,关键字synchronized可以保证同一时刻只能有一个线程执行某方法或某代码块。volatile关键字:
多线程编程中,不希望对拷贝的副本进行操作,希望直接进行原始变量的操作(节约复制变量副本与同步的时间),就可以在变量声明时使用volatile关键字。使用volatile定义的变量在进行操作时直接进行原始变量内容的处理。
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。Java内存模型有三大特性: 1、可见性:指线程间的可见性,一个线程修改的状态对另一个线程是可见的。 2、原子性:指具有不可分割性。---Java中synchronized和锁中的操作是保证原子性的。 非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。JUC中AtomicInteger、AtomicLong、AtomicReference 3、有序性:Java语言提供了volatile和synchronized两个关键字保证线程之间操作的有序性。 volatile是本身禁止指令重排序,synchronized是--一个变量在同一个时刻只允许一个线程对其进行lock操作。
使用CPU总线嗅探机制告知其它线程该变量副本已失效,需要重新从主内存中获取。
相比 synchronized 而言,volatile 可以看作是一个轻量级锁,所以使用 volatile 的成本更低,因为它不会引起线程上下文的切换和调度。但 volatile 无法像 synchronized 一样保证操作的原子性。原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。
在多线程环境下,volatile 关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性。也就是说,多线程环境下,使用 volatile 修饰的变量是线程不安全的。
对任意单个使用 volatile 修饰的变量的读 / 写是具有原子性。
为了提高性能,编译器和处理器通常会对指令进行重排序。重排序的顺序:
1、编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。2、指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。3、内存系统重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
为了实现volatile内存可见性,JMM会限制特定类型的编译器和处理器重排序。
Java 编译器在生成字节码时,会在指令序列中插入内存屏障指令来禁止特定类型的处理器重排序。 内存屏障是一组处理器指令,它的作用是禁止指令重排序和解决内存可见性的问题。 指令重排序时不能把后面的指令重排序到内存屏障之前的位置Java内存模型- JMM 的可见性问题:
JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
每个线程有自己的工作内存,保存共享变量的副本。
线程对变量的读写操作都是对自己的工作内存中操作,不能直接读写主内存中变量。 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值传递需要通过主内存中转完成。因此多线程下共享变量的可见性存在问题—解决:使用synchronized加锁、使用volatile关键字
1、加锁:进入synchronized同步代码块后,线程获得锁,会清空本地内存,然后从主内存中拷贝共享变量的最新值到本地内存作为副本。之后将修改后的副本值刷新到主内存,最后线程释放锁。 2、使用volatile关键字:使用volatile修饰共享变量,在线程操作变量副本并写会主内存后,通过CPU总线嗅探机制告知其它线程该变量副本已失效,需要重新从主内存中获取。总线嗅探机制:–实现缓存一致性
计算机中为了提高处理速度,CPU不直接与内存进行通信,而是在CPU与内存之间加入许多寄存器、多级缓存,解决CPU运算速度和内存读取速度不一致的问题。由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从内存拷贝到缓存中,CPU 直接操作的是缓存中的数据。但在多处理器下,将可能导致各自的缓存数据不一致(这也是可见性问题的由来),为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,而嗅探是实现缓存一致性的常见机制。缓存一致性问题不是由多CPU造成,而是多缓存导致的。
嗅探机制工作原理:
每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会从将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作时,会重新从主内存中把数据读到CPU缓存中。
转载地址:http://eobpz.baihongyu.com/