Java并发系列番外篇——同步机制(二)
Java提供了一种稍弱的同步机制,即volatile变量,用来确保将更新的操作通知到其他线程。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
volatile通常被比喻成”轻量级的synchronized“,也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile的主要作用是使变量在多个线程间可见。
volatile
我们开看一段代码:
public class MyThread extends Thread {
private boolean isStop = false;
private int num = 0;
@Override
public void run() {
while (isStop == false) {
System.out.println(num);
}
System.out.println("结束了:" + num);
}
public void setIsStop(boolean isStop) {
this.isStop = isStop;
}
public void setNum(int num) {
this.num = num;
}
}
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.setNum(10);
myThread.setIsStop(true);
这段代码的可能会一直保持循环,因为对于线程a来说,isStop的值可能永远不可见。代码甚至会打印出结束了:0
,因为在对num赋值之前,主线程就已经写入isStop并对线程a可见,这是一种重排序
现象(<u>在执行程序时为了提高性能,提高并行度,编译器和处理器常常会对指令做重排序</u>)。这个问题其实就是由于私有堆栈中的值和公有堆栈中的值不同步造成的。
这段代码演示了一个没要进行恰当同步的程序,它引起的后果就是:数据过期。需要注意的是:只要数据需要被跨线程共享,就要进行恰当的同步。
private boolean isStop = false;
private int num = 0;
通过volatile关键字,强制从公共内存中读取变量的值:
使用volatile关键字增加了实例变量在多个线程之间的可见性,但是依然无法确保原子性。
对于用volatile修饰的变量,jvm虚拟机只能保证从主内存加载到线程工作内存是最新的值,但是无法确保load、use、asign操作的安全性。
总结
使用synchronized和volatile来保证多线程之间操作的有序性,它们的区别如下:
- synchronized关键字保证同一时刻只允许一条线程操作。
- volatile无法保证原子性(没有同步性),只能保证可见性,附加禁止指令重排
- synchronized既可保证原子性,也可保证可见性
- synchronized有性能损耗
- synchronized会产生阻塞,synchronize实现的锁本质上是一种阻塞锁
- synchronized具有将线程工作内存中的私有变量与公共内存中的变量同步的功能
注:上述代码需要用JVM的Service模式启动JVM的server模式和client模式