解析Java中的volatile关键字,以及多线程的三个特性

多线程的三个特性

多线程要保证并发线程正确执行,必须要保证三个特性。

原子性(互斥性)

  • 一个或多个操作不能被分割,要么全部执行,要么就都不执行。

可见性

  • 多个线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值。

有序性

  • 程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序和编写顺序一致。但最终结果是一致的。

synchronized可保证原子性和可见性。但不能保证有序性。

volatile可保证可见性和禁止指令重排。但不能保证原子性。

Lock接口间接借助了volatile关键字间接地实现了可见性和有序性。


volatile

初始flag = false

创建一个线程,循环判断flag标志,如果flag为true,则结束循环

在主线程中开启线程,然后休眠2秒,再修改falg为true

public class VisibilityTest implements Runnable{
    public boolean flag = false;
    @Override
    public void run() {
        while (true) {
            if (flag) {
                System.out.println("循环结束!");
                break;
            }
        }
    }
}
public class volatileTest {

    public static void main(String[] args) {
        VisibilityTest visibilityTest = new VisibilityTest();
        new Thread(visibilityTest).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        visibilityTest.flag = true;
        System.out.println("main线程结束");
    }
}

如果falg被修改为true,此时线程中的循环应该被退出

但是结果如下

main线程结束
(程序未停止...

分析

虽然flag被修改为true,但是在子线程中并未获取到修改后的值

因为程序存在主存中,为了提高效率,线程执行后将值存储到了自己的缓存中,这样每次就不必去操作主存,从而提高效率。

主线程修改的是自己缓存中的值,而有可能还没来得及更新到主存中,也有可能子线程没来得及更新主存中的值,所以子线程看到的还是自己缓存中的内容,所以在主线程中修改了值,在子线程中并未看到。
在这里插入图片描述

那么如何解决呢?

只需要增加volatile关键字即可

  • volatile关键字保证了线程修改的变量值会立即被刷新到主存中,从而保证了变量在各个线程中都是可见的
public volatile boolean flag = false;

再次运行,程序就会立刻停止了

main线程结束
循环结束!

面试题:i++是原子操作吗?

i++是原子操作吗?

答:不是,因为 i++ 分三步执行:取值、修改、替换

测试

public class Add {
    private int num;

    public int addNum() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return this.num++;
    }
}
public class AddAddTest {
    public static void main(String[] args) {
        Add add = new Add();
        for(int i = 0; i < 10; i++) {
            new Thread(()-> System.out.print(add.addNum() + " ")).start();
        }
    }
}

预期结果应该是输出0到9不同的值,但是不保证顺序

结果

1 2 3 4 5 0 0 0 0 5

出现了连续4个0,和预期结果不符

分析

i++不是原子性操作,分三步:取值、修改、替换

在多线程下有可能会出问题,比如在A取到i的值为0的时候,还没等到修改,B也获取到了i的值为0,然后修改、替换i的值变为了1,此时又轮到了A执行,然后A修改、替换,也从0修改为1,此时就造成了错误

解决:

第一种方法:加锁

添加synchronized关键字,以保证操作同步

public class Add {
    private int num;

    public int addNum() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (this) {
            return this.num++;
        }
    }
}

第二种方式:使用原子变量

public class Add {
    private AtomicInteger num = new AtomicInteger(0);

    public int addNum() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取值并自增一
        return num.getAndIncrement();
    }
}

结果

5 2 0 3 8 9 1 4 7 6

0到9十个不同的数字,结果正确,说明此方法保证了自增操作是原子操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值