Synchronize下的volatile关键字

Synchronize下的volatile关键字

简介关键词:Synchronize与volatile

  1. Synchronize:无论是对于Synchronize同步方法异或是Synchronize块,本质是对某对象或某类加锁,让多线程进行队列化的有序地同步执行。

  2. volatile:用于修饰变量。在多线程执行过程中,禁止线程从工作内存(缓存)中读取值

volatile问题抛出:

让我们看到这样一个问题,我们设置一个含有boolean标志位的类Test,以及两个Runable接口实例,分别为MyThread1,MyThread2。
在MyThread1中通过while循环判断flag是否更改,如果更改便结束循环退出。
在MyThread2中改变flag值。
代码如下:
Test:

public class Test {
    boolean flag = true;
}

MyThread1:

public class MyThread1 implements Runnable{

    Test test;

    public MyThread1(Test test){
        this.test = test;
    }

    @Override
    public void run() {

        while (test.flag){

        }

        System.out.println(Thread.currentThread().getName()+"  我已退出");
    }

}

MyThread2:

public class MyThread2 implements Runnable{

    Test test;

    public MyThread2(Test test){
        this.test = test;
    }

    @Override
    public void run() {

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        test.flag = false;

    }
}

main函数:

public static void main(String[] args) {

    Test test = new Test();

    MyThread1 myThread1 = new MyThread1(test);
    MyThread2 myThread2 = new MyThread2(test);

    Thread thread1 = new Thread(myThread1);
    Thread thread2 = new Thread(myThread2);

    thread1.start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    thread2.start();


}
  1. 按照我们常规的想法,在在Sleep延时之后,Thread2会更改flag的值。而Thread1也会因此退出循环。
  2. 但实际上,Thread1并没有因此退出循环。
  3. 原因是Thread1并未从内存中读取flag,而是直接从工作内存中读取。所以即便是Thread2已经更新了flag的值,但Thread1工作内存中的flag也并未更新。所以便导致了Thread1陷入死循环。
    该图片来自于Java 并发:volatile 关键字解析

解决方法:

那么如何解决这样的问题呢?
很简单,使用volatile关键字。让线程不得不从主内存中读取flag值

volatile boolean flag = true;

在我们添加volatile关键字后,Thread1便可以正常退出。

在Synchronize下的volatile:

此时我们已经了解了volatile关键字的作用,那么在我们的volatile关键字中,Synchronize有着怎样的作用呢?

volatile问题抛出:
其实在我们实际使用中,volatile其实也是有一些隐患的。
例如:我们创造10条线程,每条线程都使volatile修饰的int常量增加1000000次。

public class MyThread1 implements Runnable{

    volatile int num = 0;

    @Override
    public void run() {

        for (int i = 0; i < 1000000; i++) {
            num++;
            System.out.println(Thread.currentThread().getName()+"  "+num);
        }

    }

    public static void main(String[] args) {

        MyThread1 myThread1 = new MyThread1();

        Thread[] arr = new Thread[10];

        for (int i = 0; i < 10; i++) {
            arr[i] = new Thread(myThread1);
        }

        for (int i = 0; i < 10; i++) {
            arr[i].start();
        }

    }

}

输出结果

分析:

  1. 从结果中,我们可以看到,num并没有像我们想象一样达到10000000。
  2. 这是因为volatile所修饰的int变量在自加过程中并非原子操作。这也就是说这个自加的过程可以被打断。可以被分解为:获取值,自加,赋值三个步骤。
  3. 例如当,num = 0时,Thread1获取了num的值,并自加为1,但此时在Thread1还未来得及更新主内存的num值的时候,Thread的2以及Thread3已经将线程的值更新为2,但Thread1再赋值,num的值又会重新变为1
  4. 所以,我们便需要在自加的过程中添加Synchronize关键字,让线程实现同步。

结论:

在我们使用volatile关键字时,需要注意操作是否为原子操作,以免造成线程不安全

扩展:
其实,对于原子操作,Java已经提供了Atomic原子类来解决。其中涉及了CAS机制,在不使用Synchronize的情况下,通过比较原值与当前值,不但性能高效,并且也能达到线程安全的目的。

《Java 多线程编程核心技术》高洪岩著

Java 并发:volatile 关键字解析

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ryan kk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值