线程基础知识系列(四)线程的同步2 :线程的notify-wait通信机制,以及Condition条件变量
线程基础知识系列(三)线程的同步 :同步控制,锁及synchronized
线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等
线程基础知识系列(一)线程的创建和启动 :线程的创建和启动,join(),daemon线程,Callable任务。
本篇文章主要讨论的关键字是volatile.
volatile使用场景
volatile介绍
volatile vs synchronized vs lock
1 volatile使用场景
Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
package com.threadexample.sysn; public class ViolatileTest extends Thread { boolean keepRunning = true; public void run() { while (keepRunning) { //这儿不能进行打印操作,否则会影响演示 //System.out.println("runing--"); } System.out.println("Thread terminated."); } public static void main(String[] args) throws InterruptedException { ViolatileTest t = new ViolatileTest(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println("keepRunning set to false."); } }
这个例子,想达到的效果很简单,main线程休眠1秒,关闭t线程。可运行结果演示,线程t并没有按预期正常退出。
如果使用volatile关键字修饰keepRunning 关键字,可达到预期效果。为什么呢?
2.volatile介绍
volatile的中文意思是“不稳定,反复无常的”。但是,在java领域,被翻译成“可见性”。
在java中,当volatile用于一个作用域时,java保证如下:
2.1 保证有序性
java内存模型,支持指令的重排序。为什么重排序,主要是从性能优化层面考虑的。主要的重排序包括以下:
编译器优化重排序
指令级并行重排序
内存系统重排序
重排序的结果:不会改变执行结果;从多线程角度看,其他线程执行顺序是无序的,从单线程角度看,本线程内的执行顺序是有序的。volatile限制了重排序。volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁,与互斥锁的不同点是:不能像锁一样保证原子性访问。
2.2 保证可见性
根据JMM规定,每个工作线程分别持有本地缓存。线程之间的信息通信,均需要线程的本地缓存与共享缓存进行同步。
volatile声明的变量,不需要保存到本地缓存,也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。
3.volatile vs synchronized vs lock
volatile与synchronized关键字,在功能上有些类似,但也有很多的不同。我个人比较喜欢通过对比的方式来学习,容易加深理解。
上面的演示例子,不使用volatile关键字也是可以的,可以使用同步synchronized。
3.1 使用synchronized,实现定时关闭线程效果。
package com.threadexample.sysn; public class ViolatileTest extends Thread { private boolean keepRunning = true; public void run() { while (keepRunning) { System.out.println("runing--"); } System.out.println("Thread terminated."); } public synchronized void setRunning(boolean keepRunning){ this.keepRunning=keepRunning; } public static void main(String[] args) throws InterruptedException { ViolatileTest t = new ViolatileTest(); t.start(); Thread.sleep(1000); t.setRunning(false); // t.keepRunning = false; System.out.println("keepRunning set to false."); } }
方面 | volatile | synchronized or lock |
线程安全保障 | 仅仅保证可见性 | 可见性+原子性 |
相对并发性 | 高 | 独占锁,相对低 |
使用场景 | 1.对变量的写入操作,不依赖变量的当前值 2.该变量不会与其他状态变量一起纳入不变性条件中 3.访问变量时,不需要加锁 | 访问共享可变变量时 尽量考虑使用并发容器 |
1.使用violatile保证线程安全例子,这种方式是错误的。
package com.threadexample.sysn; import java.util.concurrent.TimeUnit; public class CountTask implements Runnable { private volatile int count; public void run() { for(int i=0;i<100;i++){ count++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public int getCount(){return this.count;} public static void main(String[] args) throws InterruptedException { CountTask ct = new CountTask(); Thread t1 = new Thread(ct); Thread t2 = new Thread(ct); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(ct.getCount()); } }
这种方式不是线程安全的,因为存在状态依赖。递增操作,依赖于上一个值。
本文出自 “简单” 博客,请务必保留此出处http://dba10g.blog.51cto.com/764602/1795257
原文地址:http://dba10g.blog.51cto.com/764602/1795257