美文网首页
java并发编程艺术

java并发编程艺术

作者: 午时已到咯 | 来源:发表于2017-09-13 14:16 被阅读26次

    第一章 并发编程的挑战

    volatile

    1. lock前缀指令会引起处理器缓存会写到内存 缓存一致性机制
    2. 一个处理器的缓存会写到内存会导致其他处理器的缓存无效
      追加字节可以提升性能

    synchronize

    monitorenter和monitorexit
    数组3个字宽存储对象头 其他2字宽

    偏向锁:加锁和解锁不需要额外的消耗。如果存在锁竞争会带来额外的锁撤销的消耗。
    轻量级锁:竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程,会自旋消耗CPU
    重量级锁:线程不会使用自旋,不会消耗CPU。线程阻塞,响应时间缓慢。
    Mark Word中存储着锁

    原子操作

    总线锁
    缓存锁定

    CAS实现原子操作的三大问题

    1aba问题 2循环时间长开销大 3只能保证一个共享变量的原子操作
    除了偏向锁 jvm实现锁的方式都使用了循环CAS

    第三章 Java内存模型

    线程间通信 共享内存和消息传递
    源代码到指令序列的重排序

    1. 编译器优化的重排序
    2. 指令集并行的重排序
    3. 内存系统的重排序

    jmm通过内存屏障指令来禁止特定类型的处理器重排序。
    storeload barriers同时具有其他3个屏障的效果。确保store1数据对其他处理器变得可见。先于load2及所有后续装载指令的装载。storeloadbarrieers会使该屏障之前的所有内存访问指令完成后,才执行该屏障之后的内存访问指令。

    happens-before

    两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个
    操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一
    个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。
    happens-before的定义很微妙,后文会具体说明happens-before为什么要这么定义。

    as-if-serial

    按程序的顺序
    在不改变程序执行结果的前提下,尽可能提高并行度。

    顺序一致性

    两大特性:1线程的所有操作必须按照程序的顺序执行 2每个操作必须是原子执行且对所有线程可见

    只有当前线程把本地内存中写过的数据刷新到主内存之后,这个写操作才能对其他线程可见。
    加入synchronize后,临界区重排序。

    JMM不保证对64位的long型和double型变量的写操作具原子性,而顺序一致性模型保证对所有的内存读/写操作都具有原子性。long double的读操作必须具有原子性,写操作拆分成两个32位的写操作。

    volatile

    对一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入。
    对任意单个volatile变量的读写具有原子性,但类似volatile++这种复合操作不具有
    storestore屏障 volatile写 storeload屏障
    volatile读 loadload屏障 loadstore屏障

    锁的内存语义

    为何2happens-before 5
    CAS 具有volatile读和volatile写的内存语义
    源代码 lock前缀

    公平锁和非公平锁

    1. 释放的时候都要写一个volatile变量state
    2. 公平锁获取的时候首先去读volatile变量
    3. 非公平锁获取的时候,首先用cas更新volatile变量的值,这个操作同时具有volatile写和读的内存语义

    final域规则的重排序

    写:jmm禁止编译器把final域的写重排序到构造函数之外。
    读:在读一个对象的final域之前,一定先读包含这个final域的对象的引用。
    在构造器函数内对一个final引用的对象的成员域的写入,与随后在构造器外把这个被构造对象的引用赋值给一个引用变量,这两个操作间不能进行重排序。

    public class FinalReferenceExample {
        final int[] intArray; // final是引用类型
        static FinalReferenceExample obj;
        public FinalReferenceExample () { // 构造函数
            intArray = new int[1]; // 1
            intArray[0] = 1; // 2
        }
        public static void writerOne () { // 写线程A执行
            obj = new FinalReferenceExample (); // 3
        }
        public static void writerTwo () { // 写线程B执行
            obj.intArray[0] = 2; // 4
        }
        public static void reader () { // 读线程C执行
            if (obj != null) { // 5
                int temp1 = obj.intArray[0]; // 6
            }
        }
    }
    

    c能看到a的写入,看不到b的写入。
    保证被构造对象的引用在构造函数中没有溢出。

    JMM设计

    happens-before

    1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个之前。
    2. 如果重排序之后的执行结果,与按happens-before关系执行的结果一致,那么重排序不非法。
    • volatile的读写
    • 线程start
    • join

    双重检查锁定与延迟初始化

    public class DoubleCheckedLocking { // 1
        private static Instance instance; // 2
        public static Instance getInstance() { // 3
            if (instance == null) { // 4:第一次检查
                synchronized (DoubleCheckedLocking.class) { // 5:加锁
                    if (Instance == null) // 6:第二次检查
                    instance = new Instance(); // 7:问题的根源出在这里
                } // 8
            } // 9
            return instance; // 10
        } // 11
    }
    
        memory = allocate(); // 1:分配对象的内存空间
        instance = memory; // 3:设置instance指向刚分配的内存地址
    // 注意,此时对象还没有被初始化!
        ctorInstance(memory); // 2:初始化对象
    

    两个解决方案:

    1. 不允许2和3重排序
    2. 允许2和3重排序,但不允许其他线程看到这个重排序。

    1->volatile
    2->类初始化(类加载的锁)
    when类初始化

    • T是一个类,而且一个T类型的实例被创建。
    • T是一个类,且T中声明的一个静态方法被调用。
    • T中声明的一个静态字段被赋值。
    • T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。
    • T是一个顶级类,而且一个断言语句嵌套在T内部被执行。

    相关文章

      网友评论

          本文标题:java并发编程艺术

          本文链接:https://www.haomeiwen.com/subject/wbqwcxtx.html