一、多线程中有三个非常重要的特性
1. 原子性
在一次或者多次操作时,要么所有操作都被执行,要么所有操作都不执行
2. 有序性
程序执行的顺序按照代码的先后顺序执行,没有进行优化和顺序调整。
为了提高程序的运行效率,Java可能在JVM编译期和运行期会对代码指令进行一定的优化,不会百分之百的保证代码的执行顺序严格按照编写代码中的顺序执行,但也不是随意进行重排序,它会保证程序的最终运算结果是编码时所期望的。这种情况被称之为指令重排
3. 可见性
当一个线程对共享变量进行修改后,其他一个线程可以立即看到该变量修改后的最新值
二、说到三个特性,就不得不提Java内存模型(JMM)
JMM是一种抽象概念,并不真实存在,它描述的是一组规范。JVM运行程序的实体是线程,每个线程都有一个属于自己私有的工作内存。Java内存模型中规定了:所有变量都存储在主内存中,主内存是一块共享内存区域,所有线程都可以访问。
但是,线程对变量的读取赋值等操作必须在自己的工作内存中进行,在操作之前先把变量从主内存中复制到自己的工作内存中,然后对变量进行操作,操作完成后再把变量写回主内存。因为线程不能直接操作主内存中的变量,所以线程的工作内存中存放的是主内存中变量的一个副本而已。
三、如何保证有序性
Java内存模型的一个叫 Happens-Before 的原则了。如果两个操作的执行顺序无法从Happens-Before原则推到出来,那么可以对它们进行随意的重排序处理了。Happens-Before原则有下面这些原则,希望你能记住:
1. 程序次序原则:一段代码在单线程中执行的结果是有序的。
2. 锁定原则:一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
3. volatile变量原则:同时对volatile变量进行读写操作,写操作一定先于读操作。
4. 线程启动原则:Thread对象的start方法先于此线程的每一个动作。
5. 线程终结原则:线程中的所有操作都先于对此线程的终止检测。
6. 线程中断原则:对线程interrupt方法的调用先于被中断线程的代码检测到中断事件的发生。
7. 对象终结原则:一个对象的初始化完成先于它的finalize方法的开始。
8. 传递原则:操作A先于操作B,操作B先于操作C,那么操作A一定先于操作C。
9. 除了Happens-Before原则提供的天然有序性,我们还可以用以下几种方式保证有序性:
四、总结一下
特性 | volatile关键字 | Lock接口 | synchronized关键字 | Atomic变量 |
---|---|---|---|---|
原子性 | 无法保障 | 可以保障 | 可以保障 | 可以保障 |
可见性 | 可以保障 | 可以保障 | 可以保障 | 可以保障 |
有序性 | 一定程度保障 | 可以保障 | 可以保障 | 无法保障 |