详解java中volatile关键字

volatile的内存可见性

在多线程环境下,经常看到volatile关键字。用volatile修饰的变量,能够保持内存的可见性。而什么是内存的可见性呢?
所谓的内存可见性就是一个线程对共享变量进行修改,其他线程能够立即得到该共享变量的最新值。
为什么会出现内存的不可见性呢?这是因为每个线程都有自己的缓存。一个线程对共享变量的修改会先修改缓存,然后再写入到主存中。在这个过程中就会出现线程读取的不一致。也就是所谓的内存不可见性。
下面的代码说明内存的不可见性。

public class TestVolite {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while(true){
            if(td.isFlag()){
                System.out.println("------------------");
                break;
            }
        }
    }
}
class ThreadDemo implements Runnable {
    private  boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
    public boolean isFlag() {
        return flag;
    }
}

运行结果为:
flag=true.并一直运行没有结束。
说明一个线程已经修改了flag为true。另一个线程读取到的仍然是false.
若对flag前面加上 volatile 会输出flag=true 和 —–

volatile 不保证原子性和互斥性

虽然volatile能够保证内存的可见性,但是不能保证操作的原子性和互斥性,也就说不能替代synchronazied.


public class TestThread {
    public static void main(String[] args) {
        ThreadDemon td = new ThreadDemon();
        for (int i = 0; i < 10; i++) {
            new Thread(td).start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(td.num);
    }
}

class ThreadDemon implements Runnable {
    public volatile int num = 0;

    @Override
    public  void run() {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // TODO Auto-generated method stub
        num++;
    }

}

运行结果<=10.并不是我们所期待的10.原因就是num++操作并不是原子操作。该操作分为读取、修改、写入。在修改并没写入这个过程中,就可能有其他线程来进行读取。所以会造成结果偏小。也说明volatile并不能代替synchronized来进行同步操作。而通过synchronized加锁就能保证得到正确结果。


public class TestThread {
    public static void main(String[] args) {
        ThreadDemon td = new ThreadDemon();
        for (int i = 0; i < 10; i++) {
            new Thread(td).start();
        }
        try {
            Thread.sleep(100);  //目的是让主线程最后结束  才能得到最后结果。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(td.num);
    }
}

class ThreadDemon implements Runnable {
    public  int num = 0;

    @Override
    public synchronized void run() {
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // TODO Auto-generated method stub
        num++;
    }

}

运行结果为10.

CAS算法

那么有没有一种既不加锁又能保证结果正确的方法呢?答案是有的,那就是CAS(compare and swap)算法。CAS算法,有3个操作数,内存值V,旧的预期值A,要修改的新值B.当且仅当预期值A和内存值v相同时,将内存值V修改为B,否则什么都不做。现在的cpu提供了特殊的指令,可以自动更新共享数居,而且能够检测到其他线程的干扰。java.util.concurrent包提供了并发的操作。如AtomicIntegergetAndIncrement()方法就保证了i++的原子操作。java8中concurrenthashmap也已不采用segment锁方式,而使用CAS来操作了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值