• 多线程与高并发实战第三节


    并发程序的特性

    程序是什么?–> QQ.exe PowerPoint.exe

    进程是什么?–> 程序启动 进入内存 资源分配的基本单位

    线程是什么?–> 程序执行的基本单位

    程序如何开始运行?–> CPU 读指令 - PC(存储指令地址) ,读数据 Register ,计算, 回写, -> 下一条

    线程如何进行调度?–> linux 线程调度器(OS)操作系统

    线程切换的概念是什么?–> Context Switch CPU保存现场 执行新线程,恢复现场,继续执行原线程这样的一个过程

    线程的底层知识(可见性 有序性 原子性)

    1. 线程的执行
    2. 线程的调度(Context Switch)
      1. 一个核同一时刻,只能运行一个线程

    面试题:

    1. 是不是线程数越多,效率就越高?
    2. 单个CPU设定多线程是否有意义?

    可见性

    线程间的可见性

    MESI

    多线程提高效率,本地缓存数据,造成数据修改不可见,

    要想保证可见,要么触发同步指令,要么加上volatile,被修饰的内存,只要有修改,马上同步涉及到的每个线程

    用volatile保障可见性

    /**
     * volatile 关键字,使一个变量在多个线程间可见
     * A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
     * 使用volatile关键字,会让所有线程都会读到变量的修改值
     * 
     * 在下面的代码中,running是存在于堆内存的t对象中
     * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去
     * 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
     * 
     * 使用volatile,将会强制所有线程都去堆内存中读取running的值
     * 
     * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
     *
     * @author mashibing
     */
    package com.mashibing.juc.c_001_00_Visibility;
    
    import com.mashibing.util.SleepHelper;
    
    public class T01_HelloVolatile {
        private static volatile boolean running = true;
    
        private static void m() {
            System.out.println("m start");
            while (running) {
                //System.out.println("hello");
            }
            System.out.println("m end!");
        }
    
        public static void main(String[] args) {
    
            new Thread(T01_HelloVolatile::m, "t1").start();
    
            SleepHelper.sleepSeconds(1);
    
            running = false;
        }
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    缓存行对齐

    • 缓存行对齐
      缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高
      Disruptor

    • 认识缓存行对齐的编程技巧

      package com.mashibing.juc.c_001_02_FalseSharing;
      
      import java.util.concurrent.CountDownLatch;
      
      public class T01_CacheLinePadding {
          public static long COUNT = 10_0000_0000L;
      
          private static class T {
              private long p1, p2, p3, p4, p5, p6, p7;
              public long x = 0L;
              private long p9, p10, p11, p12, p13, p14, p15;
          }
      
          public static T[] arr = new T[2];
      
          static {
              arr[0] = new T();
              arr[1] = new T();
          }
      
          public static void main(String[] args) throws Exception {
              CountDownLatch latch = new CountDownLatch(2);
      
              Thread t1 = new Thread(()->{
                  for (long i = 0; i < COUNT; i++) {
                      arr[0].x = i;
                  }
      
                  latch.countDown();
              });
      
              Thread t2 = new Thread(()->{
                  for (long i = 0; i < COUNT; i++) {
                      arr[1].x = i;
                  }
      
                  latch.countDown();
              });
      
              final long start = System.nanoTime();
              t1.start();
              t2.start();
              latch.await();
              System.out.println((System.nanoTime() - start)/100_0000);
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
    • 需要注意,JDK8引入了@sun.misc.Contended注解,来保证缓存行隔离效果
      要使用此注解,必须去掉限制参数:-XX:-RestrictContended

      package com.mashibing.juc.c_001_02_FalseSharing;
      
      
      import sun.misc.Contended;
      //注意:运行这个小程序的时候,需要加参数:-XX:-RestrictContended
      import java.util.concurrent.CountDownLatch;
      
      public class T05_Contended {
          public static long COUNT = 10_0000_0000L;
      
      
          private static class T {
              @Contended  //只有1.8起作用 , 保证x位于单独一行中
              public long x = 0L;
          }
      
          public static T[] arr = new T[2];
      
          static {
              arr[0] = new T();
              arr[1] = new T();
          }
      
          public static void main(String[] args) throws Exception {
              CountDownLatch latch = new CountDownLatch(2);
      
              Thread t1 = new Thread(()->{
                  for (long i = 0; i < COUNT; i++) {
                      arr[0].x = i;
                  }
      
                  latch.countDown();
              });
      
              Thread t2 = new Thread(()->{
                  for (long i = 0; i < COUNT; i++) {
                      arr[1].x = i;
                  }
      
                  latch.countDown();
              });
      
              final long start = System.nanoTime();
              t1.start();
              t2.start();
              latch.await();
              System.out.println((System.nanoTime() - start)/100_0000);
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
    • 伪共享

    有序性

    CPU的乱序执行

    Disorder这个程序,证明乱序执行的确存在

    为什么会乱序?主要是为了提高效率

    线程的as-if-serial

    单个线程,两条语句,未必是按顺序执行

    单线程的重排序,必须保证最终一致性

    as-if-serial:看上去像是序列化(单线程)

    会产生的后果

    多线程会产生不希望看到的结果

    哪些指令可以互换顺序

    hanppens-before原则(JVM规定重排序必须遵守的规则)

    JLS17.4.5 (不需要记住)

    •程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。

    管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作。

    volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作。

    •线程启动规则:Thread的start( )方法先行发生于这个线程的每一个操作。

    •线程终止规则:线程的所有操作都先行于此线程的终止检测。可以通过Thread.join( )方法结束、Thread.isAlive( )的返回值等手段检测线程的终止。

    •线程中断规则:对线程interrupt( )方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt( )方法检测线程是否中断

    •对象终结规则:一个对象的初始化完成先行于发生它的finalize()方法的开始。

    •传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C

    使用内存屏障阻止乱序执行

    内存屏障是特殊指令:看到这种指令,前面的必须执行完,后面的才能执行

    intel : lfence sfence mfence(CPU特有指令)

    JVM中的内存屏障

    所有实现JVM规范的虚拟机,必须实现四个屏障

    LoadLoadBarrier LoadStore SL SS

    volatile的底层实现

    volatile修饰的内存,不可以重排序,对volatile修饰变量的读写访问,都不可以换顺序

    1: volatile i

    2: ACC_VOLATILE

    3: JVM的内存屏障

    ​ 屏障两边的指令不可以重排!保障有序!

    ​ happends-before

    ​ as - if - serial

    4:hotspot实现

    bytecodeinterpreter.cpp

    int field_offset = cache->f2_as_index();
              if (cache->is_volatile()) {
                if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
                  OrderAccess::fence();
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    orderaccess_linux_x86.inline.hpp

    inline void OrderAccess::fence() {
      if (os::is_MP()) {
        // always use locked addl since mfence is sometimes expensive
    #ifdef AMD64
        __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
    #else
        __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
    #endif
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    LOCK 用于在多处理器中执行指令时对共享内存的独占使用。
    它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。

    另外还提供了有序的指令无法越过这个内存屏障的作用。

  • 相关阅读:
    web前端期末大作业——HTML+CSS+JavaScript仿王者荣耀游戏网站设计与制作
    JWT 使用入门(二)token有效期
    JVM后端编译与优化——编译器优化技术
    Windows 系统下怎么获取 UDP 本机地址
    ARM官方推荐的JTAG/SWD接口
    动漫小可爱-网页添加L2Dwidget.js
    JavaWeb--06Vue组件库Element
    elasticsearch中文档操作,索引导入数据、批量导入、删除文档
    GET方式请求参数中文乱码问题 [JavaWeb][Servlet]
    RunnerGo:轻量级、全栈式、易用性和高效性的测试工具
  • 原文地址:https://blog.csdn.net/qq_29374433/article/details/126898688