• Java并发理论基础


    Java并发理论基础

    为什么需要多线程

    • CPU增加缓存,以均衡与内存间的速度差异. ==> 可见性问题
    • 进程,线程,分时复用CPU,均衡CPU和I/O的速度差异. ==> 原子性问题
    • 编译程序优化指令执行次序,使缓存合理利用. ==> 有序性问题

    并发出现问题的原因

    可见性

    可见性:一个线程对共享变量的修改,另外一个线程可以立即看到.

    示例:

    int i = 0;
    
    // 线程1
    i = 10;
    
    // 线程2
    j = i;
    
    // 若执行顺序是:线程2先执行,线程1再执行,则j=0
    // 若线程1先执行,线程2再执行,则j=10
    // 执行赋值语句时: 先从内存读取当前值到高速缓存中,修改后,再将数值写入到内存中.
    

    原子性

    原子性:一个操作或多个操作要么全部执行并且执行过程不被打断,要么都不执行.

    示例:转账问题:A给B转账.A的余额减少,B的余额增加.两个动作必须都成功或都失败.

    有序性

    有序性:程序执行的顺序按照代码的先后顺序执行.

    指令重排序(instruction reorder):程序中的代码顺序与实际执行的顺序并不一定是一致的.

    三种类型的重排序:

    • 编译器优化的重排序: 编译器在不改变语义的情况下,改变语句的执行顺序.
    • 指令级并行的重排序: 处理器使用指令级并行技术(Instruction-Level Parallelism,ILP)将多条指令重叠执行.(指令间不存在依赖关系)
    • 内存系统的重排序: 处理器使用缓冲区,使得操作可能是乱序的.

    流程:
    源代码 ==> 编译器优化重排序 ==> 指令级并行重排序 ==> 内存系统重排序 ==> 最终执行的指令序列

    • 编译器重排序 ==> JMM的编译器重排序该则会禁止特定类型的重排序.
    • 处理器重排序 ==> 插入特定类型的内存屏障(memory barriers).通过内存屏障禁止特定类型的重排序.

    JMM(Java内存模型)

    • 原子性: 基本类型变量的读取和赋值是原子性的(必须是数字赋值给变量,变量间相互赋值非原子操作)
    • 可见性: 使用volatile关键字保证可见性.(被volatile修饰的共享变量保证其修改后会被立即更新到内存,而普通变量修改后写入内存时间是不确定的)
    • 有序性: 可通过volatilesynchronizedLock保证有序性.(synchronizedLock保证同一时刻只有一个线程执行同步代码,相当于顺序执行.)

    Happens-Before规则

    1. 单一线程原则(Single Thread Rule): 一个线程内,靠前的操作先于靠后的操作完成.
    2. 管程锁定规则(Monitor Lock Rule): unlock操作先于同一个锁的lock操作.
    3. volatile变量规则(Volatile Variable Rule): 对一个volatile变量的写操作先于读操作.
    4. 线程启动规则(Thread Start Rule): start()方法调用先于此线程的所有动作.
    5. 线程加入规则(Thread Join Rule): 一个线程调用另外一个线程的join()方法,则该线程在另外线程执行结束后再继续执行.
    6. 线程中断规则(Thread Interruption Rule): 对线程调用interrupt()方法先于被中断的线程检测到中断事件的发生.
    7. 对象终结规则(Finalizer Rule): 一个对象的初始化咸鱼发生finalize()方法前.
    8. 传递性(Transitivity): 操作A先于操作B,操作B先于操作C,则操作A先于操作C.

    线程安全的程度

    分类:

    • 不可变
    • 绝对线程安全
    • 相对线程安全
    • 线程兼容
    • 线程对立

    不可变

    • 不可变(Immutable)对象一定是线程安全的.
    • 不可变类型:
      • final关键字修饰的基本数据类型.
      • String类型
      • 枚举类型
      • Number部分子类: Long,Double,BigInteger,BigDecimal.

    示例:

    // 使用Collections.unmodifiableList()获得一个不可变的集合
    ArrayList<Integer> list = new ArrayList<>();
    List<Integer> unmodifiableList = Collections.unmodifiableList(list);
    unmodifiableList.add(1);
    
    /* 底层直接将add改写成抛出异常
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    */
    

    绝对线程安全

    任何时刻都不需要额外的同步措施.

    相对线程安全

    • 需要保证对象单独操作时是线程安全的,在调用时不需要额外的保障措施.
    • 特定顺序的连续调用,则需要额外的同步手段保证正确性.
    • 大部分线程安全类属于这种类型.(Vector, HashTable, synchronizedCollection()等)
    • 即:调用集合的方法可以保证正确性,但对于单个线程而言,多次调用之间不能保证正确性.

    示例:

    // ConcurrentHashMap是线程安全类,但是多个指令顺序执行时,
    // 不能保证同一线程的不同指令连续执行而不被其他线程打断
    ConcurrentHashMap chm = new ConcurrentHashMap<Integer,Integer>();
    
    @Test
    public void test() throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                chm.put(i,i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                chm.put(i,i+1);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(2);
        Iterator iterator = chm.entrySet().iterator();
        while (iterator.hasNext())
            System.out.println(iterator.next());
    }
    

    线程兼容

    对象本身不是线程安全的,但通过适当的同步手段保证并发执行时正确的执行.

    对象对立

    无论采取何种同步措施,都无法在多线程下并发使用.

    线程安全的实现

    1. 互斥同步: synchronizedReentrantLock.
    2. 非阻塞同步: CAS, AtomicInteger和ABA问题.
    3. 无同步方案: 栈封闭, 线程本地存储(Thread Local Storage), 可重入代码(Reentrant Code).

    基于冲突检测的乐观并发策略:

    • 先进行操作.若没有冲突,则操作成功;否则采取补偿措施(重试).
    • 乐观锁需要操作冲突检测两个步骤是原子的.常见的原则操作是比较并交换(Compare-And-Swap,CAS).
    • CAS:两个操作数:内存地址,旧的预期值,新值. 若指定地址的值为旧值,则更新为新值,否则不进行更新.
    • AtomicInteger:通过unsafe的CAS操作实现的.
    • ABA问题:若一个变量从A变成B,然后变成A,则CAS则认为其没有改动过.
    • 使用带标记的原子引用类AtomicStampedReference,通过变量的版本保证CAS的正确性.

    无同步方案:

    • 栈封闭: 多个线程访问局部变量不会有线程安全问题,局部变量是线程私有的.
    • 线程本地存储: 保证共享数据的可见性限制在同一个线程内,则无需同步.(ThreadLocal,服务器,客户端的请求响应可以使用线程本地存储解决线程安全性问题)
    • 可重入代码/纯代码(Pure Code): 任何时刻中断,转去其他代码都不影响其运行结果.(不依赖堆上的数据和公共的资源,状态量由参数传入等)

    ThreadLocal

    • ThreadLocal有一个ThreadLocalMap对象.
    • 调用set()方法,则先得到线程的ThreadLocalMap对象,在进行插入操作.
    • 不存在多线程竞争.
    • 可能导致内存泄漏,需要手动remove().

    参考:

  • 相关阅读:
    高级映射之事务
    配置tomcat-users.xml文件
    动态SQL之标签
    性能测试
    Service
    添加 aar 或 jar 包依赖 的方式
    安卓设备 以太网代理 问题排查
    剑指offer:面试题15、链表中倒数第 K 个结点
    剑指offer:面试题14、调整数组顺序使奇数位于偶数前面
    剑指offer:面试题13、在O(1)时间删除链表结点
  • 原文地址:https://www.cnblogs.com/truestoriesavici01/p/13217274.html
Copyright © 2020-2023  润新知