学习笔记day06(多线程-多核并发缓存架构+JMM)

本文探讨了Java中volatile关键字的作用,解释了其如何保证多线程环境下的数据可见性和有序性。通过一个示例展示了在没有volatile时,线程间无法感知共享变量的更新。然后介绍了总线加锁和MESI缓存一致性协议在确保数据一致性的机制,并解释了volatile关键字在汇编层面的实现。最后,文章提到了volatile无法保证原子性,举例说明了并发场景下可能出现的问题,并指出使用synchronized关键字可以解决原子性问题。
摘要由CSDN通过智能技术生成

一、多核并发缓存架构

package cn.mrhan.java.thread;

/**
 * @Author hanYu
 * @Date 2021/5/30
 * @Description
 **/
public class VolatileVisibilityTest {
    private static boolean initFlag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("waiting data...");
                while (!initFlag){

                }
                System.out.println("=============success");
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                prepareData();
            }
        }).start();
    }

    public static void prepareData(){
        System.out.println("prepareing data...");
        initFlag = true;
        System.out.println("prepare data end...");
    }


}

点击运行 
 

waiting data...
prepareing data...
prepare data end...

运行后发现 我们期望的=============success 并没有打印出来 这便是因为主线程的共享变量 被两个线程分别加载了两个共享变量副本 当第二个线程的共享变量副本的值改变后 第一个线程并没有感知到

此时可以加上volatile 关键字

waiting data...
prepareing data...
prepare data end...
=============success

底层交互模型图:

如何保证数据一致性问题:

1)总线加锁(会影响并行程序的效率),第二个线程执行完才会释放锁

2)现在:MESI缓存一致性协议

多个CPU从主内存读取同一个数据到各自的告诉缓存,当其中某个cpu修改了缓存里的数据,

该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据的变化 从而将自己缓存里的

数据失效

总线概念:

CPU和主内存是两个独立的组件,总线是实现两者数据的交互

cpu总线嗅探机制:监听

3.volatile缓存可见性实现原理

底层实现主要是通过汇编lock前缀指令 它回锁定这块内存区域的缓存并回写到主内存

会将当前处理器缓存行的数据立即写回到系统结存

这个写回内存的操作会引起其他CPU里缓存了改内存地址的数据无效

volatile由汇编语言实现

volatile加lock 和 总线加锁的区别 ?

总线加锁 涵括了整个线程的过程 而volatile加锁 只是在store 和write加锁 锁的粒度很小 可忽略不计

并发编程三大特征:可见性、原子性、有序性

volatile保证可见性、有序性 但是不能保证原子性

package cn.mrhan.java.thread;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.CountDownLatch;

/**
 * @Author hanYu
 * @Date 2021/7/30
 * @Description
 **/
public class VolatileAtomicTest2 {
    public static volatile int num = 0;

    public static void increase(){
        num++;
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolTaskExecutor poolTaskExecutor= new ThreadPoolTaskExecutor();
        poolTaskExecutor.initialize();
        poolTaskExecutor.setCorePoolSize(10);
        poolTaskExecutor.setMaxPoolSize(30);

        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i= 0;i<10;i++){
            poolTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    for(int i =0;i<1000;i++){
                        increase();
                    }
                    countDownLatch.countDown();
                    System.out.println("countDownLatch:"+countDownLatch.getCount());
                }
            });
        }
        countDownLatch.await();
        System.out.println(num);
    }
}

预想运行结果是:10000 但是实际运行结果<=10000

为什么?

当两个线程同时对num进行赋值操作的时候,第一个线程对主内存进行存储、赋值的时候,由于缓存一致性协议,第二个线程会将工作内存中的num置为无效,此时重新读取主内存中的变量为1 之前进行+1的操作会丢失

为了保证原子性 可以加synchronized 关键字

public static synchronized void increase(){
    num++;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值