Volatile原理

1、volatile的实现可见性原理

volatile的是通过加入内存屏障和禁止指令重排序优化来实现的。

对于写而言:volatile关键修饰的变量在被写操作时,会在写操作后加入一条store指令,将当前工作内存中的数据刷新到主存中去。

对于读而言:volatile关键修饰的变量在被读操作时,会再读操作前加入一条load执行,将主存中的数据更新到当前工作内存中。

线程写volatile变量的过程:

1、改变线程工作内存中的volatile变量副本的值;

2、将修改后的副本变量更新到主内存中去。

线程读volatile变量的过程:

1、先从主内存中将最新volatile变量的最新值读取到当前线程的工作内存中;

2、线程从工作内存中获取volatile变量副本的值。


2、volatile不能保证原子性

如下代码:

public static void main(String[] args) {
    int number = 1;
    number++;
    System.out.println(number);
}

在这段代码中,number++是一段线程不安全的代码,这一行代码可以拆分成如下三步操作:

1、读取number的值

2、将number的值加1

3、将加后的number写回内存中

要想实现原子性使用synchronized关键字是可以实现的

synchronized (this) {
    number++;
}

如果为number变量增加上volatile关键字显然无法实现原子性。

代码:

public class VolatileDemo3 {

    private volatile int number = 0;

    private Lock lock = new ReentrantLock();

    public int getNumber() {
        return this.number;
    }

//    public synchronized void increase() {
    public void increase() {
//        try {
//            Thread.sleep(100);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

//        lock.lock();
//        try {
            this.number++;
//        } finally {
//            lock.unlock();
//        }
    }

    public static void main(String[] args) {
        int a = 1;
        long startTime = System.currentTimeMillis();
        for (;;) {
            int i = volatileTest(a);
            a ++;
            if (i < 500) {
                System.out.println("在第" + a + "次,获得的结果为" + i);
                break;
            } else if(i >= 10000) {
                System.out.println("已经十万次,获得结果为" + i);
                break;
            }
        }
        long endTime = System.currentTimeMillis();

        System.out.println("共耗时:" + (endTime - startTime) / 1000);
    }

    public  static int volatileTest(int a) {
        VolatileDemo3 volatileDemo3 = new VolatileDemo3();
        long startTime = System.currentTimeMillis();
        for (int i = 0;i < 500;i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    volatileDemo3.increase();
                }
            }).start();
        }
        /**
         * 打印当前线程列表
         */
//        Thread.currentThread().getThreadGroup().list();
        /**
         * 这个循环的意思是当所以线程还有大于1个线程在执行,主线程都让出执行权,当所有线程执行完程序才向下执行
         * 这里在idea开发工具里面需要设置大于2,在eclipse里面只需要设置1就行了,因为idea还有一个监控线程
         */
        while(Thread.activeCount() > 2) {
            Thread.yield();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("第"+ a +"循环启用500个线程,共耗时:" + (endTime - startTime));


        return volatileDemo3.number;
    }
}

上面的代码是启动循环,在循环中启动500个线程同时执行++的操作,由于++是线程不安全的,且volatile无法保证原子性,在循环过程中可能出现小于500的情况,主要原因可以分析下number++的原理

number++线程不安全的情况:

1、线程1获得CPU执行权,需要执行++操作,发现number是volatile修饰的,先从主存中获取number的最新值为0到当前线程工作内存中;

2、此时,线程2获得CPU执行权,线程1进入等待状态,线程2发现number是volatile修饰的,先从主存中获取number的最新值为0到当前线程工作内存中;

3、线程2执行了++操作,并且将number的值更新到主存中,此时线程2的工作内存和主存的number都为1;

4、线程1重新获得执行权,将线程1工作内存中的number值加1后,并刷新到主存中,由于线程1的工作内存还为0,所以刷新到主存后主存的结果为1

这样就出现了加了两次,但是结果还是1的情况。

结果方案:

1、使用Synchronized关键字

使用关键字有两种形式,一个是锁方法,另外一个是锁代码块

方法1:

public synchronized void increase() {
    this.number++;
}

方法2:

public void increase() {
    synchronized (this) {
        this.number++;
    }
}

2、使用Lock类

这种方式是使用jdk1.5中的ReentrantLock类

private Lock lock = new ReentrantLock();
public synchronized void increase() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.lock();
        try {
            this.number++;
        } finally {
            lock.unlock();
        }
}

3、使用AtomicInteger

AtomicInteger这个类是在jdk1.5以后出现的一个原子性加减的类,使用方式如下:

private AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.getAndIncrement();//实现++操作
atomicInteger.get();//获取当前值

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值