Java线程-volatile不能保证原子性

下面是一共通过volatile实现原子性的例子:

通过建立100个线程,计算number这个变量最后的结果。

package com.Sychronized;

public class VolatileDemo {

    private volatile int number=0;
    public int getNumber()
    {
        return this.number;
    }
    public void increase()
    {
        this.number++;
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final VolatileDemo volDemo=new VolatileDemo();
        for(int i=0;i<100;i++)
        {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    volDemo.increase();
                }
            }).start();
        }
        
        //如果还有子线程在运行,主线程就让出CPU资源
        //直到所有的子线程都运行完了,主线程再继续往下执行
        while(Thread.activeCount()>1)
        {
            Thread.yield();
        }
        
        System.out.println("number:"+volDemo.getNumber());
    }

}

运行结果:

发现有几种结果:

造成这个结果的原因就是,volatile关键字具有可见性,number++实际上有三步操作,但是不具备原子性。

 

 

程序分析:

number++包含三步操作:1,读取number的值,2,number的值加,3,写入number的值给number变量

假如当前nubmer=5

1,线程A读取number的值

2,线程B读取number的值

3,线程B执行加1操作

4,线程B写入最新的number的值。

此时:主内存:number=6,线程B工作内存:number=6,线程A工作内存:number=5。

5,线程A执行加1操作

6,线程A写入最新的number值。

两次number++操作,只增加了1

 

解决方案:

1,使用synchronized关键字

2,使用ReentrantLock(java.until.concurrent.locks包下)

3,使用AtomicInterger(java.util.concurrent.atomic包下)

可以看这个文章:

Java并发编程:volatile关键字解析:http://www.importnew.com/18126.html

第一种:

    private int number=0;
    public int getNumber()
    {
        return this.number;
    }
    public synchronized void increase()
    {
        this.number++;
    }

或者:

    private int number=0;
    public int getNumber()
    {
        return this.number;
    }
    public  void increase()
    {
        synchronized(this)
        {
            this.number++;
        }
    }

第二种:

对执行加锁处理的地方使用 try finally。。操作,在finally里面,执行lock.unlock释放锁

    private int number=0;
    private Lock lock = new ReentrantLock();
    public int getNumber()
    {
        return this.number;
    }
    public  void increase()
    {
        lock.lock();
        try {
            this.number++;
        }finally {
            lock.unlock();
        }
    }

 

volatile使用场合

synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

1)对变量的写操作不依赖于当前值

  • .不满足:number++,count=count*5等
  • .满足:boolean变量,记录温度变化的变量等

2)该变量没有包含在具有其他变量的不变式中

  • 不满足:不变式:low<up

.实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行

 

volatile使用场景

(同样摘自文章Java并发编程:volatile关键字解析:http://www.importnew.com/18126.html)

1.状态标记量

volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}
volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.double check

class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

 

 

synchronized和volatile比较

  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;
  • 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于解锁。
  • synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。

 

posted @ 2018-01-20 17:02  美好的明天  阅读(603)  评论(0编辑  收藏  举报