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

本文深入探讨并发编程中的线程特性,包括线程的执行、调度和切换,以及缓存对齐、伪共享和乱序执行的影响。重点解析了volatile关键字在保证线程间可见性中的作用,防止因本地缓存导致的数据不一致。同时,介绍了内存屏障的概念,用于防止乱序执行,并分析了JVM中的内存屏障实现。通过对这些概念的理解,有助于提升多线程程序的性能和正确性。
摘要由CSDN通过智能技术生成

并发程序的特性

程序是什么?–> 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;
    }
}



缓存行对齐

  • 缓存行对齐
    缓存行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);
        }
    }
    
    
  • 需要注意,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);
        }
    }
    
    
  • 伪共享

有序性

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();
            }

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
  }
}

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值