这里有新鲜出炉的 Java 并发编程示例,程序狗速度看过来!
java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方,感兴趣的朋友一起看看吧
概述
Java 语言中关键字 volatile 被称作轻量级的 synchronized,与 synchronized 相比,volatile 编码相对简单且运行的时的开销较少,但能够正确合理的应用好 volatile 并不是那么的容易,因为它比使用锁更容易出错,接下来本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方。
为何使用 volatile?
(1)简易性:在某些需要同步的场景下使用 volatile 变量要比使用锁更加简单
(2)性能:在某些情况下使用 volatile 同步机制的性能要优于锁
(3)volatile 操作不会像锁一样容易造成阻塞
volatile 特性
(1)volatile 变量具有 synchronized 的可见性特性,及如果一个字段被声明为 volatile,java 线程内存模型确保所有的线程看到这个变量的值是一致的
(2)禁止进行指令重排序
(3)不保证原子性
注:
① 重排序:重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段
② 原子性:不可中断的一个或一系列操作
③ 可见性:锁提供了两种主要特性:互斥和可见性,互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。
volatile 的实现原理
如果对声明了 volatile 的变量进行写操作,JVM 就会向处理器发送一条 Lock 前缀的指令,该 Lock 指令会使这个变量所在缓存行的数据回写到系统内存,根据缓存一致性协议,每个处理器都会通过嗅探在总线上传输的数据来检查自己缓存的值是否已过期,当处理器发现自己的缓存行对应的地址被修改,就会将当前处理器的缓存行设置成无效状态,在下次访问相同内存地址时,强制执行缓存行填充。
正确使用 volatile 的场景
volatile 主要用来解决多线程环境中内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,就无法解决线程安全问题。如:
1、不适合使用 volatile 的场景(非原子性操作)
(1)反例
- private static volatile int nextSerialNum = 0;
- public static long generateSerialNum() {
- return nextSerialNum++;
- }
这个方法的目的是要确保每次调用都返回不同的自增值,然而结果并不理想,问题在于增量操作符(++)不是原子操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,如果第二个线程在第一个线程读取旧值和写回新值期间读取这个域,第二个线程与第一个线程就会读取到同一个值。
(2)正例
其实面对上面的反例场景可以使用 JDK1.5 java.util.concurrent.atomic 中提供的原子包装类型来保证原子性操作
- private static AtomicInteger nextSerialNum = new AtomicInteger(0);
- public static long generateSerialNum() {
- return nextSerialNum.getAndIncrement();
- }
2、适合使用 volatile 的场景
在日常工作当中 volatile 大多被在状态标志的场景当中,如:
要通过一个线程来终止另外一个线程的场景
(1)反例
- private static boolean stopThread;
- public static void main(String[] args) throws InterruptedException {
- Thread th = new Thread(new Runnable() {
- @Override
- public void run() {
- int i = 0;
- while (!stopThread) {
- i++;
- }
- }
- });
- th.start();
- TimeUnit.SECONDS.sleep(2);
- stopThread = true;
- }
运行后发现该程序根本无法终止循环,原因是,java 语言规范并不保证一个线程写入的值对另外一个线程是可见的,所以即使主线程 main 函数修改了共享变量 stopThread 状态,但是对 th 线程并不可见,最终导致循环无法终止。
(2)正例
- private static volatile boolean stopThread;
- public static void main(String[] args) throws InterruptedException {
- Thread th = new Thread(new Runnable() {
- @Override
- public void run() {
- int i = 0;
- while (!stopThread) {
- i++;
- }
- }
- });
- th.start();
- TimeUnit.SECONDS.sleep(2);
- stopThread = true;
- }
通过使用关键字 volatile 修饰共享变量 stopThread,根据 volatile 的可见性原则可以保证主线程 main 函数修改了共享变量 stopThread 状态后对线程 th 来说是立即可见的,所以在两秒内线程 th 将停止循环。
总结
本文通过对 volatile 的特性介绍,以及 volatile 的实现原理,最后结合 volatile 的特性举例说明它在使用过程中应该注意的使用规则,好了,希望本文对您有所帮助!
来源: http://www.phperz.com/article/17/0818/338447.html