java并发-Volatile关键字

本文深入解析了Java中Volatile关键字的特性和作用,探讨了其如何保障变量在多线程环境下的可见性,并通过实例说明Volatile不能保证原子性。同时,文章提供了使用Volatile关键字的常见场景。
摘要由CSDN通过智能技术生成

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

转载于:https://www.cnblogs.com/csuwater/p/5411116.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值