一、JMM模型与volatile详解

一、JMM模型与volatile详解
二、synchronized原理详解
三、AQS框架详解——AbstractQueuedSynchronizer
四、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue和DelayQueue学习总结
五、CountDownLatch、CyclicBarrier和Semaphore的比较
java中interruptor理解与使用
Java线程的6种状态及切换
MESI协议:保证可见性,无法保证原子性
java并发编程脑图

一、JMM模型

JMM是java内存模型的简称,是一个抽象的概念,它是围绕原子性、有序性、可见性展开的。
一、JMM模型与volatile详解_第1张图片
(1)主内存是共享内存区域,所有变量都存在主内存中,所有线程都可以访问;
(2)各个线程的工作线程相互独立和隔离;
(3)线程操作变量必须先从主内存中读取变量信息,然后在工作内存中进行修改,最后再写会主内存中,修改才会生效。

二、JMM与硬件内存架构的关系

一、JMM模型与volatile详解_第2张图片
从硬件层面描述一下多线程执行过程:
cpu中一个核执行线程A,发现需要用到数据a,就会首先在寄存器中查找,发现没有,就会接着去L1中寻找,接着找L2,L3,如果还没有就到主内存中加载数据,计算完成后,再写回主内存中。

三、原子性,可见性和有序性

八大原子操作

(1)lock
(2)unlock
(3)read
(4)load
(5)use
(6)assign
(7)store
(8)write

一、JMM模型与volatile详解_第3张图片

1、原子性

一个操作过程是不可中断,一旦开始操作,中间不能被其他线程干扰。
比如java对于基本数据类型的操作是原子性的,但是对于long/double等数据类型的操作是非原子性的。
volatile无法保证原子性

package com.并发专题.writereadlock;

import java.util.concurrent.CountDownLatch;

/**
 * @author
 * @date 2020/9/4
 */
public class VolatileNotAtomic {
    private volatile static int count = 0;
   private static  Object object =new Object();
    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 10; j++) {
         Thread thread = new Thread(new Runnable() {
              @Override
              public void run() {
                  for (int i = 0; i < 1000; i++) {
                          count++;
                  }
              }
          });
         thread.start();
        }
       //这里主线先睡眠2s,等待其他线程完成操作;此处不睡眠的话,结果不正确
       Thread.sleep(2000);
        System.out.println(count);
    }
}

synchronized和lock可以实现原子性操作。
synchronized和lock通过加锁,使得同一时间只有一个线程访问共享变量,实现原子性和可见性。

2、可见性

可见性是线程A修改了某个共享变量c,线程B能够马上感知到。

package com.volatile关键字可见性;

/**
 * @author
 * @date 2020/9/2
 */
public class TestVolatileVisible {
    private static  boolean initFlag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("线程A开始工作");
            while(!initFlag){

            }
            System.out.println("线程A====感觉到flag发生了变化。。。。。");
        },"threadA").start();

        Thread.sleep(5000);

        new Thread(()->{
            refreshFlag();
        },"threadB").start();
    }

    private static void refreshFlag() {
        initFlag = true;
    }
}

volatile、synchronized和lock可以实现可见性。

volatile字节码层面通过ACC_*实现可见性;

使用javap -v TestVolatileVisible.class查看字节码文件。
flags: ACC_STATIC

硬件层面通过:LOCK指令实现;
早期LOCK指令直接锁住总线,
后来优化为MESI缓存一致性协议实现

CAS的形式修改中内存中的值。

3、有序性

了解有序性之前,我们需要首先知道java中会存在指令重排现象,因为cpu和IO之间的速率差异,再汇编层面甚至更底层会在不影响代码执行结果准确性的前提下,对我们的代码重新排序执行。由于某种原因(我觉得是硬件重排序做的不够完美)导致再多线程下,这种从排序会带来执行结果的不确定性,所以我们在多线程场景下编写代码时,需要自己控制代码的有序性,禁止底层的指令重排。

java内存模型也规定了一些先天的代码先后顺序,我们称为“happens-before”原则。但是该原则之外的指令,cpu是可以进行重新排序的。

四、volatile内存语义

(1)保证可见性
(2)禁止指令重排

1、volatile的可见性

2、volatile无法实现原子性

3、volatile禁止重排

1、volatile:java代码
2、ACC_VOLATILE:字节码
3、JVM内存屏障 :规范
4、hospot实现;C++代码
JVM层面是通过内存屏障实现的。
CPU层面通过LOCK指令实现,Lock指令早起是通过对总线加锁实现一致性,后来通过MESI协议实现同步。

如果在代码中间插入一个内存屏障,那么不管什么指令都不能和这个屏障进行排序,可以实现禁止内存屏障前后指令排序的功能;
内存屏障还能强制刷出各种CPU的缓存数据,让其他线程中的共享变量失效。

4、volatile在DCL(双重判断的单例模式)中的经典应用

private static DCL instance;

虽然有双重锁保证了单例模式,但是这种写法在多线程场景下依然有问题,原因就是:

instance=new DCL();

这句java语法在汇编层面是三条语句:

1、memory= allocate()//申请内存
2instance(memory)//初始化对象
3、instance=memory //指针指向

很不幸,在happens-before原则下,指令2和3可以重排;多线程场景下会导致其他线程获取到没有完全实例话的对象,引起问题。

这时候我们就可以将代码修改为;

private volatile static DCL instance;

通过volatile关键字来禁止指令2和指令3的重排。

你可能感兴趣的:(Java并发编程,java,编程语言,jvm,多线程,cpu)