Volatile关键字最大的特点是,在多线程中保持变量的可见性。这个可见性主要是指变量在主存中的可见性。因此,有必要了解java内存模型。
java内存模型
java中每个线程有一块工作内存区,存放着被所有线程共享的主内存中的变量的值的拷贝。一个线程可以执行的操作有:使用(use)、赋值(assign)、装载(load)、存储(store)、锁定(lock)、解锁(unlock)。
主内存可执行的操作有读、写、锁定、解锁,每个操作都是原子的。一个变量是java程序可以存取的一个地址,它可以是基本类型、引用类型变量等。保存在主内存区的变量可以被所有线程共享,但一个线程存取另一个线程的参数或者局部变量是不可能的。
其中:使用(use)、赋值(assign)、锁定(lock)、解锁(unlock),这些操作都是原子操作,但是主内存与线程之间的数据传递不具有原子性,例如:当数据从主存中到工作内存时,需要两个动作:
1:主存执行读(read)操作;
2:工作内存执行相应的load操作;
下边我们看看上述各个操作的含义:
1:use:把一个变量在线程工作内存的拷贝内容传送给线程执行引擎;
2:assign:把一个值从线程执行引擎传送给变量的线程工作内存;
3:read:把一个变量的主内存拷贝传送到线程的工作内存,以便线程的load操作进行;
4:load:把read操作从主内存中得到的值放入线程内存;
5:store:把一个变量的线程拷贝内容传送到主内存,以便write操作使用;
6:write:把store操作的变量放入主内存;
7:主内存的lock操作是线程获得一个独占锁;
8:主内存的unlock操作是线程释放一个独占锁。
volatile关键字
1:volatile关键字的作用:
- 其它线程对变量的修改,可以及时反应在当前线程上;
- 确保当前线程对volatile变量的修改,能立即写回主内存,被其它线程所见,即保证变量的可见性;
- volatile声明的变量,编译器会保证其有序性。
public class Volatile {
public volatile static boolean stop=false;
public static void main(String[] args) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
public void run() {
Volatile.test();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
stop=true;
}
}).start();
}
public static void test()
{
while(!stop)
{
System.out.println("线程"+Thread.currentThread().getName()+"还在运行");
}
if(stop)
{
System.out.println("线程"+Thread.currentThread().getName()+"结束运行");
}
}
}
2.volatile不保证原子性
public class Volatile {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Volatile test = new Volatile();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
运行发现,每次的运行结果不一样,这是由于inc自增不是原子性的。
可以改变这个现状有两个方法:
1:采用synchronized:
public class Test {
public int inc = 0;
public synchronized void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
2:Lock
public class Test {
public int inc = 0;
Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
inc++;
} finally{
lock.unlock();
}
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
4.volatile的原理和实现机制
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
使用volatile关键字的场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
1.状态标记量
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
2:volatile 关键字来保证多线程下的单例
public class Singleton {
private volatile Singleton instance = null;
public Singleton getInstance() {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
引用块内容
http://www.cnblogs.com/dolphin0520/p/3920373.html
java程序性能优化
java核心技术 卷I