Java多线程是Java基础的重要的一部分,支持多线程是Java的重要特性之一. 主要包括如下内容:
volatile用处
在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量. 为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较,在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写,这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致,
volatile 指示JVM这个变量不稳定,每次使用它都到主存中进行读取,一般说来,多任务环境下,各任务间共享的变量都应该加volatile修饰符,
volatile 修饰的成员变量在每次被线程访问时, 都强迫从共享内存中重读该成员变量的值, 而且, 当成员变量发生变化时, 强迫线程将变化值回写到共享内存, 这样在任何时刻, 两个不同的线程总是看到某个成员变量的同一个值,
使用同步解决变量不一致问题
内存可见性:
在一个任意对象上执行同步语句,目的是为了让该线程在进入和离开同步代码块时,将该线程中的所有变量的私有拷贝与共享内存中的原始值进行比较,从而发现没有用volatile标记的变量所发生的变化
加锁(synchronized同步)的功能不仅仅局限于互斥行为, 同时还存在另外一个重要的方面:内存可见性, 我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态, 而且还希望确保当一个线程修改了对象状态后, 其他线程能够看到该变化, 而线程的同步恰恰也能够实现这一点, 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果, 为了确保所有的线程都能看到共享变量的最新值, 可以在所有执行读操作或写操作的线程上加上同一把锁, 下图示例了同步的可见性保证,

当线程A执行某个同步代码块时, 线程B随后进入由同一个锁保护的同步代码块, 这种情况下可以保证, 当锁被释放前, A看到的所有变量值(锁释放前 , A看到的变量包括y和x)在B获得同一个锁后同样可以由B看到, 换句话说, 当线程B执行由锁保护的同步代码块时, 可以看到线程A之前在同一个锁保护的同步代码块中的所有操作结果, 如果在线程A unlock M之后, 线程B才进入lock M, 那么线程B都可以看到线程A unlock M之前的操作, 可以得到i=1,j=1, 如果在线程B unlock M之后, 线程A才进入lock M, 那么线程B就不一定能看到线程A中的操作, 因此j的值就不一定是1
volatile同步机制
volatile 是一种稍弱的同步机制,在访问 volatile 变量时不会执行加锁操作,也就不会执行线程阻塞,因此 volatile 变量是一种比 synchronized 关键字更轻量级的同步机制,
在两个或者更多的线程需要访问的成员变量上使用volatile, 当要访问的变量已在synchronized代码块中, 或者为常量时, 没必要使用volatile, 由于使用volatile屏蔽掉了JVM中必要的代码优化, 所以在效率上比较低, 因此一定在必要时才使用此关键字
实际现象
对于非volatile修饰的变量, 尽管jvm的优化, 会导致变量的可见性问题, 但这种可见性的问题也只是在短时间内高并发的情况下发生, CPU执行时会很快刷新Cache, 一般的情况下很难出现, 而且出现这种问题是不可预测的, 与jvm、机器配置环境等都有关
正确使用Volatile变量
当且仅当满足以下所有条件时,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
- 该变量没有包含在具有其他变量的不变式中
关于正确使用Volatile请参考: Java 理论与实践: 正确使用 Volatile 变量
【参考文献】:
- 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)
- 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)
- 《深入Java虚拟机——JVM高级特性与最佳实践》
- Java 理论与实践: 正确使用 Volatile 变量