JMM:Java内存模型

9 篇文章 0 订阅
5 篇文章 0 订阅

五、JMM:Java内存模型

(1)什么是JMM?

Java 内存模型是一种规范,定义了很多东西:

  • 所有的变量都存储在主内存(Main Memory)中。
  • 每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝副本。
  • 线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存。
  • 不同的线程之间无法直接访问对方本地内存中的变量。

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

JMM主要是为了规定了线程和内存之间的一些关系,是深入了解Java并发编程的先决条件

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存;

2、线程加锁前,必须读取主存中的最新值到工作内存中;

3、加锁和解锁是同一把锁;

JMM中规定内存交互操作有8种

线程中分为 工作内存、主内存线程之间要通信必须通过主内存

  1. lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态。
  2. unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  3. read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  4. load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中。
  5. use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令。
  6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中。
  7. store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
  8. write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

在这里插入图片描述

于是就会产生多线程并发时的线程安全问题:原子性有序性可见性

(2)线程安全:原子性、有序性和见性

原子性: 提供互斥访问,同一时刻只能有一个线程对数据进行操作;例如:atomicXXX类,synchronized关键字的应用。

有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序;例如,happens-before原则。

可见性: 一个线程对主内存的修改可以及时地被其他线程看到;例如:synchronized,volatile。

CAS
  • 使用atomic原子类解决原子类性问题:谈起原子性肯定离不开众所周知的Atomic包,JDK里面提供了很多atomic类,AtomicInteger,AtomicLong,AtomicBoolean等等。
class AtomicIntegerExample {
    private static final Logger log = LoggerFactory.getLogger(AtomicIntegerExample.class);
    // 请求总数
    public static int requestTotal = 500;
    // 并发执行的线程数
    public static int threadTotal = 20;

    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();//获取线程池
        final Semaphore semaphore = new Semaphore(threadTotal);//定义信号量
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        for (int i = 0; i < requestTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
    }
}

AtomicInteger中的incrementAndGet方法就是乐观锁的一个实现,CAS:比较当前|工作内存|中的值 和 |主内存|中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁

关键方法:incrementAndGet()

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

public final int getAndAddInt(Object var1, long var2, int var4) {
     int var5;
     do {
     //获取当前对象内存的值
         var5 = this.getIntVolatile(var1, var2);
     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

     return var5;
 }

首先通过调用getIntVolatile()方法,使用对象的引用与值的偏移量得到当前值,然后调用compareAndSwapInt检测如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作。

compareAndSwapInt()希望达到的目标是对于var1对象,如果当前的值var2和底层的值var5相等,那么把它更新成后面的值(var5+var4).

原子类可以解决原子性问题,当还是会ABA问题。可以用版本号来解决;

(2)对Volatile 的理解

Volatile 是 Java 虚拟机提供 轻量级的同步机制

  • volatile可以保证可见性;

    一个线程对主内存的修改可以及时的被其他线程看到;

  • 不能保证原子性

    • 互斥访问,同一时刻只能有一个线程对数据进行操作,如atomic类、synchronize关键字的应用
    • 原子性的底层核心思想是CAS,但是CAS中存在ABA问题(可用版本号来解决->乐观锁)
  • 避免指令重排。由于内存屏障,可以保证避免指令重排的现象产生

    • 一个线程观察其他线程中的指令顺序,由于指令重排,改观察结果一般杂乱无章
    • volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

可以使用原子类atomic

image-20200812215909271

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

原子性的底层核心思想是CAS,但是CAS中存在ABA问题(可用版本号来解决->乐观锁)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值