Java多线程—volatile关键字
Java并发编程三大特性
在Java并发编程中,有三个概念:1. 原子性 2. 可见性 3. 有序性。
原子性(Atomicity)
原子性指一个操作是不可中断的,要么全部执行成功要么全部执行失败,在数据库的事务中也有这个性质。
例如,a++,对于一个共享变量a的操作实际上可以分为三个步骤:1. 读取变量a的值;2. 将a的值+1;3. 将值重新赋值给a。此操作是可分割的,所以这并不是一个原子操作。像这样的操作,如果在多线程下,就有可能会产生异常,所以需要使用synchronized
或者Lock
来保证代码的原子性。
可见性(Visibility)
可见性就是指当一个线程修改了共享变量的值,对于另外一个线程来说是可见的,其他线程能够立即得知这个修改。
Java内存模型中,线程通信是通过共享内存的方式进行的,为了加快线程通信的速度,线程一般是不会直接操作内存的,而是操作当前线程内的一个副本。这样,如果线程对变量的操作没有刷新内存的话,仅仅改变了当前线程中的副本,对于其他线程来说,就是不可见的。
volatile关键字的作用就是保证了变量的可见性,volatile变量可以保证对它的修改可以立即被刷新到主存,而当其他线程需要读取该值的时候,会从内存中去读取新的值。
另外,synchronized同样具有可见性,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。
有序性(Ordering)
指程序执行的顺序应该按照代码的先后顺序执行。在Java内存模型中,是允许编译器和处理器对指令进行重排序的,但是重排序不会影响到单线程程序的运行,在单线程程序中,JVM重排序的时候会保证程序的最终执行结果和顺序执行代码的结果是一样的。但是对于多线程程序来说,这就不一定了。
例如下面的例子:
//Thread 0
context = loadContext();
inited = true;
//Thread 1
while (!inited) {
sleep;
}
doSomethingwithconfig(context);
在这个例子中,如果Thread0出现了重排序:
inited = true;
context = loadContext();
在这种情况下,线程B就有可能拿到一个未初始化的content去执行其他方法,从而引起错误。
为了防止重排序,需要使用volatile关键字,它可以保证对变量的操作是不会被重排序的。
内心OS:要是真出了这样的BUG,不清楚有序性还真不知道咋改…
volatile保证可见性
public class VolatileDemo {
private static boolean isThreadRunning = true;
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(() -> {
System.out.println("Thread0开始");
while (isThreadRunning) { }
System.out.println("Thread0结束");
});
Thread thread1 = new Thread(() -> {
System.out.println("isThreadRunning被修改...");
isThreadRunning = false;
});
thread0.start();
Thread.sleep(2000);
thread1.start();
}
}
运行结果:
可以看到,程序陷入了死循环,虽然isThreadRunning的值被Thread1修改了,然而Thread0并没有接收到修改的值,这就是因为变量值没有被写到主存,导致本地内存中的值一直为true。
解决方法就是为isThreadRunning变量加上volatile关键字。
private volatile static boolean isThreadRunning = true;
再次运行程序,现在就不会导致死循环了。
volatile与原子性
volitile
关键字是不能保证原子性的,看下面一个例子:
public class VolatileDemo {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++;
System.out.println(count);
}
}).start();
}
}
}
从下面的输出结果可以看出数据产生了异常。
如果将count++
操作改为sychronized操作就可以保证输出结果的正确了。
synchronized (lock) {
count++;
}
总结
- volitile关键字是synchrnized的轻量级实现,volitile性能优于synchrnized。
保证输出结果的正确了。
synchronized (lock) {
count++;
}
总结
- volitile关键字是synchrnized的轻量级实现,volitile性能优于synchrnized。
- volitile可以保证数据的可见性和有序性,而synchroized和Lock三种特性都可以保证。ti