volatile 关键字
说明volatile 关键字之前,首先先简单介绍下java内存模型,因为后续的介绍与java的内存模型息息相关。
详细的就不说明了,百度上都有,简单的说下。
Java 内存模型中的可见性、原子性和有序性。
可见性: 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。
原子性: 原子是世界上的最小单位,具有不可分割性。
有序性: 即程序执行的顺序按照代码的先后顺序执行。
volatile,从字面上说是易变的、不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,
只要是被此关键字修饰的变量都是易变的、不稳定的
volatile关键字的两个特点:
(1)、内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。
(2)、可以禁止指令重排序
首先说下第一个volatile特点,假如不加这个关键字会怎么样,如:
public class test{
private int key;
public int getKey(){
return key;
}
public void setKey(int key){
this.key= key;
}
}
在这个代码中,test不是线程安全的,因为key的set/get方法都是在没有同步的线程中执行的,假如说有两个线程同步执行,
线程1执行了set方法,线程2的get方法有可能取到值也有可能取不到值,解决办法,就是在成员变量那块加上volatile关键字,这样的话,利用volatile关键字第一个特性,当线程1执行了set方法的时候,线程2的get方法也可以取到值。
以下有个列子,
说明了volatile保证不了原子性这个特点:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
/**
* 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
* 2)禁止进行指令重排序。
* **/
public volatile int inc = 0; //没有达到预期原因是volatile关键字保证了java内存模型中的可读性,但递增操作保证不了原子性
public void increaseVolatile() {
inc++;
}
public static void main(String[] args) {
final test4 test = new test4();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++){
test.increaseVolatile();
}
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println("volatile------"+test.inc);
/**
* 结论:
* volatile关键字可以保证目标的可读性,但是保证不了目标的原子性
*
* **/
}
}
通过上面的这段代码可以猜出,正常来说它的值应该等于10000,但是运行出的结果却是可能等于10000,也有可能小于10000
如何解决,可以采用synchronized关键字达到预期目的,或者采用Lock达到预期目的,再或者采用AtomicInteger达到预期目的,下面是个对比的列子可以看成可视化的结果:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
/**
* 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
* 2)禁止进行指令重排序。
* **/
public volatile int inc = 0; //没有达到预期原因是volatile关键字保证了java内存模型中的可读性,但递增操作保证不了原子性
public int incc = 0; //采用synchronized关键字达到预期目的
public int inccc = 0; //采用Lock达到预期目的
Lock lock = new ReentrantLock();
public AtomicInteger incccc = new AtomicInteger(); //采用AtomicInteger达到预期目的
public synchronized static void increaseSynchronized() { //这里包含相关类锁和对象锁的区别的知识,当有多个线程共同应 //用的时候,使用类锁。
incc++;
}
public void increaseVolatile() {
inc++;
}
public void increaseLock() {
lock.lock();
try {
inccc++;
} finally{
lock.unlock();
}
}
public void increaseAtomicInteger() {
incccc.getAndIncrement();
}
public static void main(String[] args) {
final test4 test = new test4();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++){
test.increaseSynchronized();
test.increaseVolatile();
test.increaseLock();
test.increaseAtomicInteger();
}
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println("volatile------"+test.inc);
System.out.println("synchronized-------"+test.incc);
System.out.println("Lock-------"+test.inccc);
System.out.println("AtomicInteger--------"+test.incccc);
/**
* 结论:
* volatile关键字可以保证目标的可读性,但是保证不了目标的原子性
*
* **/
}
}
效果:
volatile关键字的第二个特性应用场景:
1.状态标记量
/**
* volatile关键字的应用场景(利用它的有序列性)
* 1.状态标记量
* 2.double check 双重检查
* **/
volatile boolean flag = false;
public void volatileYy(){
while(!flag){
//.....
}
}
public void setFlag() {
flag = true;
}
2.double check 双重检查