在java线程并发处理中,关键字volatile的主要作用是使变量在多个线程间可见。那么volatile到底该怎么用了?我们首先来看一段代码:
public class MyThread1 implements Runnable {
private boolean istag = true;
public boolean isIstag() {
return istag;
}
public void setIstag(boolean istag) {
this.istag = istag;
}
public void print() {
try {
while (istag) {
System.out.println("print()线程名称是:"+Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (Exception e) {
}
}
@Override
public void run() {
print();
}
}
public class MyThread { public static void main(String[] args) { MyThread1 m=new MyThread1(); new Thread(m).start(); System.out.println("我要停止循环-->"+Thread.currentThread().getName()); m.setIstag(false); }}
此段代码如果运行,我们可以发现线程能正常停止。如果代码运行在-server服务器模式中64bit的JVM上时,会出现死循环。解决的方案是用 volatile关键字。关键字volatile的作用是强制从公共堆栈取得变量的值,而不是从线程私有数据栈中取得变量的值。
我们将代码改造下:
public class MyThread1 extends Thread {
private volatile boolean istag = true;
public boolean isIstag() {
return istag;
}
public void setIstag(boolean istag) {
this.istag = istag;
}
@Override
public void run() {
System.out.println("进入run()====");
while (istag) {
System.out.println("线程名称是:"+Thread.currentThread().getName());
}
System.out.println("线程被停止====");
}
}
使用了volatile关键字增加了实例变量在多线程之间的可见性。但volatile关键字最致命的缺点是不支持原子性。
关键字synchronized跟volatile进行一下比较:
- 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字执行效率打打的提高,在开发中使用synchronized还是比较多的。
- 多线程访问volatile不会发生堵塞,而synchronized可能会出现堵塞。
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做了同步。
- 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
volatile非原子的特性
public class MyThread1 extends Thread {
private volatile static int count;
public void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count="+count);
}
@Override
public void run() {
addCount();
}
}
public class RunTest { public static void main(String[] args){ MyThread1[] myArray=new MyThread1[100]; for (int i = 0; i < 100; i++) { myArray[i]=new MyThread1(); } for (int i = 0; i < 100; i++) { myArray[i].start(); } }}
public class MyThread1 extends Thread {
private static int count;
//需要加static,才达到同步效果
public synchronized static void addCount(){
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count="+count);
}
@Override
public void run() {
addCount();
}
}
关键字volatile主要使用的场合是在多线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新的值。
关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但需要注意的是:如果修改实例变量中的数据,比如i++,也就是i=i+1,这样的操作并不是一个原子操作,也就是非线程安全的。表达式i++的实际操作步骤是:
- 从内存中读取i的值;
- 计算i的值;
- 将i的值写入到内存中。
所以说volatile本身并不处理数据的原子性,而是强制对数据读写及时影响到主内存的。
由上我们可以得出结论:
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
Java中使用volatile的场景:
package com.ztz.myThread;
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}