入坑JAVA多线程并发(七)volatile到底是什么

多线程中有三个特性:

1、原子性:不可分割的一个操作,类似于数据库的事物。
如下代码:
> int a,b;
a = 0;      //1
b = a;      //2
a = a+1;     //3
a++;         //4

上面4个语句中只有1是原子性操作,语句2:先取得a的值,再赋给b,语句3:取得a的值,再加1,最好赋给a;语句4:取得a的值,加1,再赋给a。只有简单的读取和赋值是属于原子性的操作。

2、可见性:保证每个线程访问的变量都是最新的,

  java提供volatile来保证可见性,在多线程中,多个线程访问同一个变量,当其中一个变量修改了变量的值,之后其他线程都能知道这个修改,确保取得最新的值,而不是工作内存中的副本。如不理解java内存模式,请参考入坑JAVA多线程并发(三)同步锁synchronized

3、有序性

  代码的执行顺序并不是完全按照代码的先后位置,在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
  java内存模式会根据happens-before保证从排序不会影响到单线程中代码最后的结果。
  java提供volatile来保证一定的有序性。
  如下:

int a = 3;
int b = 4
int c = a + b;

  happens-before会保证语句3会在1和2的后面执行,因为c=a+b依赖a和b的值,但是语句1和语句2并没有任何依赖关系,可能先执行了语句2再执行语句1。
  而volatile就是保证代码的有序性,如果变量a被volatile修饰,那么语句1一定会在2和3之前执行,如果b被volatile修饰,则可以保证语句2一定会在它上面所有的代码执行完才开始执行,而且在语句2执行完之前不会执行语句2后面的语句。

实例
理论说完了,直接上代码:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1("线程1");
        thread1.start();
        Thread.sleep(100);
        thread1.setFlag(false);
    }
}

class Thread1 extends Thread{

    private boolean flag = true;

    public Thread1(String name){
        super(name);
    }

    public void setFlag(boolean flag1){
        flag = flag1;
    }
    @Override
    public void run() {
        while(flag){
            //System.out.println("flag="+flag);
        };
        System.out.println("跳出死循环");
    }
}

没有任何输出,虽然flag的值在100ms之后被修改了,但是并没有同步到线程1的工作内存中,导致在线程1中的flag的值一直是true,所以还在死循环中,
这里如果把run方法的注释代码打开可以退出循环,这是因为在访问flag的时候会刷新工作内存的值,检测到flag值为false,退出循环。
如果把flag的值用volatile修饰,就不会出现上面的情况,
修改如下:

class Thread1 extends Thread{

    private volatile boolean flag = true;

    public Thread1(String name){
        super(name);
    }

    public void setFlag(boolean flag1){
        flag = flag1;
    }
    @Override
    public void run() {
        while(flag){

        };
        System.out.println("跳出死循环");
    }
}

在main线程修改flag的值后会立刻退出循环。

volatile不能保证原子性

volatile使用起来比synchronized简单很多,但是volatile并不能替代synchronized,因为volatile不能保证原子性,
如下:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1("线程1");
        Thread1 thread2 = new Thread1("线程2");
        Thread1 thread3 = new Thread1("线程3");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
        System.out.println(Thread1.num);

    }
}

class Thread1 extends Thread{

    public static volatile int num = 0;

    public Thread1(String name){
        super(name);
    }

    @Override
    public   void run() {
        for(int i = 0;i<1000;i++){
            num++;
        }
    }
}

正常情况下应该输出3000;但是运行多次输出都是在1000-2000之间,正如上面对于原子性的介绍,num++这个语句不是原子性的,因为是volatile修饰的属性,所以步骤是:
1、从主内存获取num的值
2、对num进行加1操作,赋值给num
3、把numd值同步到主内存
一个线程在步骤1或者2时,有其它线程对num也进行修改,就导致num最终的值小于3000。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值