Volatile实现可见性原理
Volatile关键字
- 能够保证Volatile变量的可见性
- 不能保证Volatile变量复合操作的原子性
Volatile实现内存可见性
深入来说:通过加入内存屏障和禁止重排序优化来实现
- 对于Volatile变量执行写操作时,会在写操作后加入一条store屏障指令
- 对于Volatile变量执行写操作时,会在读操作前加入一条load屏障指令
线程写Volatile变量的过程:
- 改变线程工作内存中Volatile变量副本的值
- 将改变后的副本的值从工作内存刷新到主内存
线程读Volatile变量的过程:
- 从主内存中读取Volatile变量的最新值到线程的工作内存中
- 从工作内存中读取Volatile变量的副本
Volatile不能保证Volatile变量的复合操作的原子性
代码演示
public class VolatileTest {
private volatile int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
number++;
}
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileTest.increase();
}
}).start();
}
//如果还有子线程在运行,主线程就让出cpu
//直到所有的子线程都运行完了,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileTest.getNumber());
}
}
代码分析
解决方案:保证number自增操作的原子性
方式1:synchronized
public class VolatileTest {
private /*volatile*/ int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//方式一
synchronized (this){
number++;
}
}
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileTest.increase();
}
}).start();
}
//如果还有子线程在运行,主线程就让出cpu
//直到所有的子线程都运行完了,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileTest.getNumber());
}
}
方式2:ReentrantLock
public class VolatileTest {
private /*volatile*/ int number = 0;
private Lock lock = new ReentrantLock();
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void increase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// //方式一
// synchronized (this){
// number++;
// }
//方式2
lock.lock();
try{
number++;
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileTest.increase();
}
}).start();
}
//如果还有子线程在运行,主线程就让出cpu
//直到所有的子线程都运行完了,主线程再继续往下执行
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("number:" + volatileTest.getNumber());
}
}
Volatile适用场合
要在多线程中安全的使用 Volatile变量,必须同时满足:
- 对变量的写入操作不依赖于其当前值
不满足:number++、count=count*5
满足:boolean变量、记录温度变化的变量等 - 该变量没有包含在具有其他变量的不变式中
不满足:不变式low<up
Synchronized与Volatile比较
- volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
- 从内存可见性角度,volatile读相当于加锁,volatile写相当于解锁
- synchroinized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性