并发(一):JMM内存模型和volatile关键字

JMM内存模型

每个Java线程都有⾃⼰的⼯作内存。操作数据,⾸先从主内存中读,得到⼀份拷⻉,操作完毕后再写回到主内存。
在这里插入图片描述
JMM可能带来可⻅性、原⼦性和有序性问题。
所谓可⻅性,就是某个线程对主内存内容的更改,应该⽴刻通知到其它线程。
所谓原⼦性,是指⼀个操作是不可分割的,不能执⾏到⼀半,就不执⾏了。
所谓有序性,就是指令是有序的,不会被重排。

volatile关键字

volatile 关键字是Java提供的一种轻量级的同步机制,它能保证可见性和有序性,但是不能保证原子性。

可见性

先做一个可见性的测试
结果1:在没有加 volatile 时,主线程获取到的number值一直为0,所以while死循环在这,Thread B 一直不会执行。
结果2:加了 volatile 时,当Thread A 将number值改为60 后,number值立马同步到主内存,并且主线程在使用前都会从主内存中拿到最新的值,所以立马跳出while死循环,Thread B 执行了。

public static void main(String[] args) {

        // 可见性测试
        volatileVisibilityDemo();

    }

    private static void volatileVisibilityDemo() {
        MyData data = new MyData();

        new Thread(() -> {
            /**
             * 1.首先让是Thread A 睡眠3秒,此时Thread B 获取到的number值就是0,由于不可见性,Thread B中一直都是0,所以死循环卡在这儿
             *
             * 2.但是加了volatile关键字,Thread A 和Thread B 一开始获取的number值都是0,A线程睡眠3秒后将number改为60,
             * number值立马同步到主内存,以及每次使用前立即从主内存刷新,所以立马跳出while的死循环,Thread B 执行。
             */
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t 执行了.");
            data.setTo60();
            System.out.println(Thread.currentThread().getName() + "\t 更新后的num值:" + data.number);
        }, "Thread A").start();

        while (data.number == 0) {
            //如果number的值一直为0,则说明 Thread B没有 Thread A 线程的可见性

            /**
             * 如果这里面打印东西或者睡眠,此时就算不加volatile关键字,Thread B 也会执行
             * 因为 println 和 sleep 方法都是同步的方法,也就是说会让CPU强制推送最新的值,刷新工作内存中的缓存
             */
            //System.out.println("xxxx");
            /*try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 执行了.");
            data.setTo60();
            System.out.println(Thread.currentThread().getName() + "\t 更新后的num值:" + data.number);
        }, "Thread B").start();
    }
    
class MyData {
    int number = 0;
    //volatile int number = 0;
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void setTo60() {
        this.number = 60;
    }

    // 虽然number加了volatile关键字,但是不能解决原子性问题
    // 可以加 synchronized,性能差不推荐
    public void addPlusPlus() {
        number++;
    }

    public void addAtomic() {
        atomicInteger.getAndIncrement();
    }
}
原子性

再做个原子性的测试,在多线程环境下对number值一直累加
结果1:虽然用了 volatile 关键字,但是累加的值还是不对,因为++操作不是一个原子操作,通过javap -c 名称我们得到汇编码可以看得出来
在这里插入图片描述
结果2:要想得到正确的累加结果,可以借助JUC包中的原子类,也就是AtomicInteger,他的由CAS保证的。

private static void atomicDemo() {
        MyData data = new MyData();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    data.addPlusPlus();
                    data.addAtomic();
                }
            }, "Thread" + i).start();
        }

        // 等上面的20个线程都执行完毕后,下面的main主线程才执行
        // 如果超过两个线程(main主线程和另一个Monitor线程),则主线程变为就绪状态,让出CPU执行时间,让别的线程都执行完毕
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println("最后的number值为:" + data.number);
        System.out.println("原子类最后的值为:" + data.atomicInteger.get());
    }
有序性

再做个有序性的测试,最终的结果有可能是5也有可能是6

	int a = 0;
	boolean flag = false;
	
	/**
	* 多线程下flag=true可能先执⾏,还没⾛到a=1就被挂起。
	* 其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
	*/
	public void method01() {
	    a = 1;
	    flag = true;
	}
	
	public void method02() {
	    if (flag) {
	        a += 5;
	        System.out.println("*****最终值a: " + a);
	    }
	}
	
	public static void main(String[] args) {
	    ResortSeqDemo resortSeq = new ResortSeqDemo();
	    new Thread(resortSeq::method01, "ThreadA").start();
	    new Thread(resortSeq::method02, "ThreadB").start();
	}

volatile是通过内存屏障来保证有序性的,相当于该指令前后都加上了内存屏障,也就是告诉CPU和编译器,不管什么指令都不能和这条内存屏障指令重排序,也就是说可以禁止内存屏障前后的指令进行重排序优化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值