Java多线程初学者指南(6):慎重使用volatile关键字
- 格式:pdf
- 大小:56.64 KB
- 文档页数:3
正确使用volatile 的模式很多并发性专家事实上往往引导用户远离volatile 变量,因为使用它们要比使用锁更加容易出错。
然而,如果谨慎地遵循一些良好定义的模式,就能够在很多场合内安全地使用volatile 变量。
要始终牢记使用volatile 的限制——只有在状态真正独立于程序内其他内容时才能使用volatile ——这条规则能够避免将这些模式扩展到不安全的用例。
模式#1:状态标志也许实现volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。
很多应用程序包含了一种控制结构,形式为“在还没有准备好停止程序时再执行一些工作”,如清单2 所示:清单2. 将volatile 变量作为状态标志使用volatile boolean shutdownRequested;...public void shutdown() { shutdownRequested = true; }public void doWork() {while (!shutdownRequested) {// do stuff}}很可能会从循环外部调用shutdown() 方法——即在另一个线程中——因此,需要执行某种同步来确保正确实现shutdownRequested 变量的可见性。
(可能会从JMX侦听程序、GUI 事件线程中的操作侦听程序、通过RMI 、通过一个Web 服务等调用)。
然而,使用synchronized块编写循环要比使用清单2 所示的volatile 状态标志编写麻烦很多。
由于volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用volatile。
这种类型的状态标记的一个公共特性是:通常只有一种状态转换;shutdownRequested 标志从false 转换为true,然后程序停止。
这种模式可以扩展到来回转换的状态标志,但是只有在转换周期不被察觉的情况下才能扩展(从false 到true,再转换到false)。
javavolatile关键字作⽤及使⽤场景详解1. volatile关键字的作⽤:保证了变量的可见性(visibility)。
被volatile关键字修饰的变量,如果值发⽣了变更,其他线程⽴马可见,避免出现脏读的现象。
如以下代码⽚段,isShutDown被置为true后,doWork⽅法仍有执⾏。
如⽤volatile修饰isShutDown变量,可避免此问题。
public class VolatileTest3 {static class Work {boolean isShutDown = false;void shutdown() {isShutDown = true;System.out.println("shutdown!");}void doWork() {while (!isShutDown) {System.out.println("doWork");}}}public static void main(String[] args) {Work work = new Work();new Thread(work::doWork).start();new Thread(work::doWork).start();new Thread(work::doWork).start();new Thread(work::shutdown).start();new Thread(work::doWork).start();new Thread(work::doWork).start();new Thread(work::doWork).start();}}出现脏读时,运⾏结果如下:2. 为什么会出现脏读?Java内存模型规定所有的变量都是存在主存当中,每个线程都有⾃⼰的⼯作内存。
线程对变量的所有操作都必须在⼯作内存中进⾏,⽽不能直接对主存进⾏操作。
并且每个线程不能访问其他线程的⼯作内存。
volitle原理Volatile原理Volatile,中文翻译为“易变的”,是程序设计中一个重要的关键字。
在多线程编程中,volatile关键字的作用是保证被修饰的变量在多个线程之间的可见性,即一个线程对该变量的修改对其他线程是可见的。
本文将详细介绍volatile原理、使用场景以及注意事项。
一、volatile原理在介绍volatile原理之前,我们先来了解一下计算机的存储模型。
计算机在执行程序时,会将变量从内存加载到CPU的寄存器中进行操作,完成后再写回内存。
由于CPU的速度远远快于内存,为了提高效率,CPU可能会对读写操作进行重排序。
但是,重排序可能会导致多线程程序出现意想不到的错误。
volatile关键字的作用就是告诉编译器和CPU,对该变量的读写操作不能进行重排序。
当一个变量被声明为volatile时,每次访问该变量时,都必须从内存中读取最新的值,而不是使用寄存器中的备份。
二、volatile的使用场景1. 控制变量的可见性:当多个线程需要同时访问一个共享变量时,为了保证各个线程看到的变量值是一致的,需要将该变量声明为volatile。
2. 状态标志位:在多线程环境下,使用volatile变量作为状态标志位是一种常见的做法。
例如,线程A负责数据的生产,线程B负责数据的消费,通过设置volatile标志位来控制线程的执行顺序和退出条件。
3. 双重检查锁定(Double-Checked Locking):在单例模式中,为了确保只创建一个实例,可以使用双重检查锁定来提高性能。
在多线程环境下,需要将实例变量声明为volatile,以保证多个线程看到的实例是同一个。
三、volatile的注意事项1. volatile不能保证原子性:volatile关键字只能保证变量的可见性,无法保证多个线程对变量的操作是原子性的。
如果需要保证原子性,可以使用synchronized关键字或者Lock锁。
2. volatile不能替代锁:虽然volatile可以保证变量的可见性,但是无法保证线程安全。
一、java中volatile关键字的作用在Java中,volatile是一种轻量级的同步机制,主要用于多线程之间的变量可见性和防止指令重排序。
二、volatile关键字的使用场景1. 适用于多线程对共享变量的操作在多线程环境下,如果一个变量被多个线程操作并且这些线程之间没有同步的机制,那么很有可能会出现数据不一致的情况。
此时可以使用volatile关键字来保证变量的可见性,即当一个线程修改了这个变量的值,其他线程可以立即看到最新的值。
2. 适用于简单的标志位控制当一个变量在多个线程中被频繁修改,但是对这个变量的修改并不依赖于当前值时,可以使用volatile关键字来提高性能。
一个标志位用于控制线程的启停,这种情况下可以使用volatile来保证线程之间的及时通知。
三、volatile关键字的使用注意事项1. 不保证原子性Volatile关键字只能保证变量的可见性和禁止指令重排序,但不能保证原子性。
如果一个操作是原子性的,那么使用volatile是不够的,需要配合使用synchronized或者Lock来保证原子性操作。
2. 可能存在有序性问题在JDK1.5之前,volatile关键字可能存在指令重排序的问题,导致代码执行的顺序和预期不符。
但在JDK1.5及以后,Java语言的内存模型对volatile的语义做出了修订,可以保证变量的有序性。
3. 要注意内存可见性对于volatile修饰的变量,当一个线程修改了这个变量的值,其他线程会立即看到最新的值。
这种特性使得volatile变量适合作为控制变量,但在一些复杂的操作场景下仍需谨慎使用。
四、volatile关键字的使用示例```javapublic class VolatileExample {private volatile boolean flag = false;public void toggleFlag() {flag = !flag;}public boolean isFlag() {return flag;}}```以上代码展示了一个简单的示例,其中flag变量被volatile修饰,保证了其可见性。
java多线程volatile关键字分享
大家知道java多线程volatile关键字吗?JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile。
volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程。
从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块。
以前一直没在意,一直以为volatile修饰了就高枕无忧了,但其实不然,最简单的一个场景:
public class Counter {
public volatile static int count = 0;
public static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
}
public static void main(String[] args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i new Thread(new Runnable() {
@Override
public void run() {。
Volatile的作用与使用场景1. 引言在计算机科学中,多线程编程是一种常见的方式,它可以提高程序的并发性和性能。
然而,多线程编程也带来了一些潜在的问题,其中之一就是共享变量的可见性和一致性问题。
为了解决这个问题,Java引入了volatile关键字来确保共享变量的可见性和一致性。
本文将详细介绍volatile关键字的作用、使用场景以及注意事项。
2. volatile关键字的作用2.1 可见性在多线程环境下,每个线程都有自己的工作内存,其中包含了主内存中共享变量的副本。
当一个线程修改了共享变量时,它可能只是修改了自己工作内存中该变量的副本,并没有立即将修改后的值写回主内存。
其他线程读取该共享变量时可能读取到旧值,导致数据不一致。
而使用volatile关键字修饰一个共享变量时,每次访问该变量时都会从主内存中读取最新值,并且每次修改后都会立即写回主内存。
这样可以确保所有线程对该变量的读写操作都能看到最新值,从而解决了可见性问题。
2.2 原子性除了可见性,volatile关键字还可以保证特定操作的原子性。
在Java中,对基本数据类型(除了long和double)的读写操作是原子的,即每次读写都是一次完整的操作。
但是对于多次读写组合起来的复合操作,就不一定是原子的了。
例如,下面这段代码就不是原子操作:int count = 0;count++;虽然对int类型变量的读写操作都是原子的,但是上述代码实际上包含了两个步骤:先读取变量值,然后加1后再写回变量。
如果多个线程同时执行这段代码,就可能出现线程安全问题。
而如果将count声明为volatile int count = 0;,则该复合操作就具有原子性。
即使多个线程同时执行该代码块,也能保证结果正确。
3. volatile关键字的使用场景3.1 控制状态标志位在多线程编程中,经常需要使用状态标志位来控制程序流程。
例如,在一个生产者-消费者模型中,生产者线程在队列满时需要等待消费者消费一部分数据后再继续生产;而消费者线程在队列空时需要等待生产者生产数据后再进行消费。
解析Java中volatile关键字解析Java中volatile关键字解析Java中volatile关键字在java多线程编程中经常volatile,有时候这个关键字和synchronized 或者lock经常有人混淆,具体解析如下:在多线程的环境中会存在成员变量可见性问题:java的每个线程都存在一个线程栈的内存空间,该内存空间保存了该线程运行时的变量信息,当线程访问某一个变量值的时候首先会根据这个变量的地址找到对象的堆内存或者是栈堆存(原生数据类型)中的具体的内容,然后把这个内同赋值一个副本保存在本线程的线程栈中,紧接着对这个变量的一切操作在线程完成退出之前都和堆栈内存中的变量内容是没有关系的,操作的是自己线程栈中的'副本。
当操作完后会把操作完的结果写回到主内存中。
假如有两个线程A和B,同事操作某一个变量x;A对x进行了加1操作,那么B获取的副本可能是x加1后的结果,也可能是x;为了保证获取内存中最新的数据变量需要加上volatile 关键字,这样在每次对x进行操作的时候都会去检查下线程栈中的变量的值是不是和住内存中变量的值一样,如果不一样会重新load。
eg:public class ThreadSee { //t1线程会根据flag的值做对应的操作,主线程会更改t1的值public static void main(String[] args) throws InterruptedException { ThReadT est th= new ThReadTest(); Thread t1 = new Thread(th); t1.start(); Thread.sleep(1000); th.changeFlag(); T hread.sleep(2000); System.out.println(th.getFlag()); } } class ThReadTest implements Runnable{ //线程访问变量时会把其load 到对应的线程栈中,每次操作时都要获取内存中最新的数据 private volatile boolean stopflag; @Override public void run() { int i=0; while(!stopflag){ i++; System.out.println("=="+Thread.currentThread().getName()); } System.out.println("Thread finish:"+i); } public void changeFlag(){ this.stopflag=true; System.out.println(Thread.cu rrentThread().getName()+"***********"); } public boolean getFlag(){ return stopflag; } }上述代码如果去掉volatile,会一直死循环执行下去。
Java多线程volatile关键字volatile的应⽤Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM⾥,JVM执⾏字节码,最终需要转化为汇编指令在CPU上执⾏,Java中所使⽤的并发机制依赖于JVM的实现和CPU的指令volatile是个轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性"。
可见性的意思就是当⼀个线程修改⼀个共享变量时,另外⼀个线程能读到这个修改的值。
如果volatile变量修饰符使⽤恰当的话,⽐synchronized的使⽤和执⾏成本更低,因为它不会引起线程上下⽂的切换和调度。
volatile的定义Java编程语⾔允许线程访问共享变量,为了确保共享变量能被准确和⼀致地更新,线程应该确保通过排他锁单独获得这个变量。
Java语⾔提供了volatile,某些情况下⽐所更⽅便。
如果⼀个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是⼀致的。
volatile的内存语义当声明共享变量为volatile后,对这个变量的读/写将会很特别。
锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对⼀个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写⼊。
锁的语义决定了临界区代码的执⾏具有原⼦性。
这意味着即使是64位的long型和double型变量,只要是volatile变量就具有原⼦性。
多个volatile整体上不具有原⼦性。
简⽽⾔之,volatile变量⾃⾝具有下列特性:可见性:对⼀个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写⼊原⼦性:对任意的单个volatile变量的读/写具有原⼦性,但类似于volatile++这种复合操作不具有原⼦性。
Java多线程之volatile关键字及内存屏障实例解析前⾯⼀篇⽂章在介绍Java内存模型的三⼤特性(原⼦性、可见性、有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇⽂章就来介绍volatile关键字的内存语义以及实现其特性的内存屏障。
volatile是JVM提供的⼀种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两⼤特性:可见性和有序性。
正因为volatile关键字具有这两⼤特性,所以我们可以使⽤volatile关键字解决多线程中的某些同步问题。
volatile的可见性volatile的可见性是指当⼀个变量被volatile修饰后,这个变量就对所有线程均可见。
⽩话点就是说当⼀个线程修改了⼀个volatile修饰的变量后,其他线程可以⽴刻得知这个变量的修改,拿到最这个变量最新的值。
结合前⼀篇⽂章提到的Java内存模型中线程、⼯作内存、主内存的交互关系,我们对volatile的可见性也可以这么理解,定义为volatile修饰的变量,在线程对其进⾏写⼊操作时不会把值缓存在⼯作内存中,⽽是直接把修改后的值刷新回写到主内存,⽽当处理器监控到其他线程中该变量在主内存中的内存地址发⽣变化时,会让这些线程重新到主内存中拷贝这个变量的最新值到⼯作内存中,⽽不是继续使⽤⼯作内存中旧的缓存。
下⾯我列举⼀个利⽤volatile可见性解决多线程并发安全的⽰例:public class VolatileDemo {//private static boolean isReady = false;private static volatile boolean isReady = false;static class ReadyThread extends Thread {public void run() {while (!isReady) {}System.out.println("ReadyThread finish");}}public static void main(String[] args) throws InterruptedException {new ReadyThread().start();Thread.sleep(1000);//sleep 1秒钟确保ReadyThread线程已经开始执⾏isReady = true;}}上⾯这段代码运⾏之后最终会在控制台打印出: ReadyThread finish ,⽽当你将变量isReady的volatile修饰符去掉之后再运⾏则会发现程序⼀直运⾏⽽不结束,⽽控制台也没有任何打印输出。
Java并发编程之关键字volatile知识总结⽬录⼀、作⽤⼆、可见性三、有序性四、happens-before五、与 Synchronized 对⽐⼀、作⽤被 volatile 修饰的变量1.保证了不同线程对该变量操作的内存可见性2.禁⽌指令重排序⼆、可见性Java 内存模型(Java Memory Model)是 Java 虚拟机定义的⼀种规范,即每个线程都有⾃⼰的⼯作空间,线程对变量的操作都在线程的⼯作内存中完成,再同步到主存中,这样可能会导致不同的线程对共享变量的操作,在各⾃线程⼯作空间内不⼀样的问题。
⽽⽤ volatile 修饰的变量,线程对该变量的修改,会⽴刻刷新到主存,其它线程读取该变量时,会重新去主存读取新值。
三、有序性CPU 为了提供程序的运⾏效率,会对代码的执⾏顺序进⾏重排,因此代码中各个语句的先后执⾏顺序有可能会变化,但是它会保证程序最终执⾏结果与代码顺序执⾏结果⼀致。
指令重排序会考虑数据之间的依赖性,不会影响单个线程内程序的执⾏结果c = 2 可能在 a = 1之前执⾏int a = 1;int c = 2;volatile 修饰的变量具有有序性,即被 volatile 修饰的变量,在其前⾯的操作与在其后⾯的操作的执⾏顺序不能打乱。
int a= 1;volatile b = 2;int c = 2;a = 1 ⼀定在 c = 2 前⾯执⾏,volatile 的作⽤好像在两者之间插⼊了⼀个内存屏障对于 volatile 的有序性应⽤,⼀般常⽤ double-check 的单例模式来说明public class Singleton {private Singleton(){}private static volatile Singleton singleton;public static Singleton getSingleton(){if (singleton == null){synchronized(Singleton.class){if (singleton == null){singleton = new Singleton();}}}return singleton}}singleton = new Singleton(); 分为 3 步1.new Singleton() 开辟堆内存空间2.初始化 singleton 对象3.将 singleton 对象指向堆内存地址第 3 步可能在第 2 步之前执⾏,那么其它线程可能得到为初始化完成的 singleton 对象,造成异常。
volatile关键字相信了解Java多线程的读者都很清楚它的作用。
volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。
如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。
但这有一定的限制。
例如,下面的例子中的n就不是原子级别的:
如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别
的操作。
原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:
如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:
上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized 关键字进行方法同步。
因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n=m+ 1,这个就是原级别的。
所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile.。