共享变量可见性实现的原理
java 语言层面支持的可见性实现方式: synchronized volatile
1、 synchronized 的两条规定:
1 线程解锁前,必须把共享变量的最新值刷新到主内存中。
2 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁需要是同一锁)
线程解锁前对共享变量的修改在下次加锁时对其他线程可见。
2、 volatile 实现可见性
深入来说,通过加入内存屏障和禁止重排序优化来实现 的。
对volatile变量执行写操作时,会在写操作后加入一条store屏障指令。
对volatile变量执行读操作时,会在读操作前加入一条load 屏障指令。
通俗地讲,volatile 变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会
强迫线程将最新的值刷新到主内存中,这样任何时刻,不同的线程总能看到该变量的最新值 。
但volatile 不能保证变量的原子性。
典型的num++ 操作,使用volatile修饰变量时,无法保存操作的原子性,所以会出现很多值的情况。
如果出现这种情况,还需要使用volatile时解决办法就是:
使用synchronized 关键字 进行 包装。ReentrantLock 进行代码前的加锁,然后使用unlock 释放锁。
在使用volatile 的时候注意专场合:
- 1、对变量的写入操作不依赖其当前的值。典型的就是numb++ \ num=num*3 等 。
- 2、该变量没有包含在具有其他变量的不变式中。(例如两个volatile 变量 存在大小关系中,或其他关系中等)
3、 synchronized 和 volatile 比较
- 1、volatile 不需要加锁,比synchronized 更经量级,不会阻塞线程。
- 2、从内存可见性角度讲,volatile 读相当于加锁,volatile 写相当于解锁。
- 3、synchronized 即能保证可见性,又能保证原子性,而volatile 只能保证可见性,无法保证原子性。
代码示例:
package org.apple.thread; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class VolatileTest { private Lock lock = new ReentrantLock(); private volatile int number = 0; public void increase(){
this.number++;
// 第二种解决办法
// lock.lock();
// this.number++;
// lock.unlock();
// 第一种解决办法
//synchronized(this){
// this.number++;
//}
} public int getNumber(){ return this.number;
} /**
* @param args
*/
public static void main(String[] args) {
final VolatileTest volatileTest = new VolatileTest();
for (int i = 0; i < 500; i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileTest.increase();
}
}).start();
}
// 如果当前线程还有子线程在运行,主线程则让出cpu 资源,直到所有的子线程运行完成后,则往下继续执行。
if(Thread.activeCount()>1){
Thread.yield();
}
System.out.println(Thread.activeCount());
System.out.println(volatileTest.getNumber());
} }