Java并发编程的艺术-----第三章读书笔记

版权声明:欢迎转载大宇的博客,转载请注明出处: https://blog.csdn.net/yanluandai1985/article/details/82759569

3.1

        原文:Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另外一个线程可见。P22

        Java内存模型JMM定义了写到本地内存中的数据何时刷新到主存中

        原文:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程的共享变量的副本。P22

        线程之间的共享变量存储在主存中,共享变量的副本存储在线程的本地内存(私有内存)。

3.1.2

        JMM内存模型(重点掌握绘画)

        

3.1.3(了解)

        从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序。

3.1.4

        内存可见性:线程私有内存及时刷新到主存中,保证其它线程能够及时获取变量最新的值

3.1.5

        happens-before: A happens-before B ,表示A操作的结果对B操作可见。可以简单理解为 A在B之前执行(但不一定)

3.2.2

        数据依赖:写后读 a=1; b=a;   写后写 a=1;a=2;   读后写a=b;b=1;

        两个操作访问同一个变量并且有写操作,那么这两个操作就称之为有数据依赖。

        编译器和处理器不会改变有数据依赖关系的两个操作的顺序

3.2.2

        编译器和处理器可能会对操作进行重排序。不管怎么重排序,程序的运行结果不允许被改变。

        重排序的目的是在不该程序执行结果的前提下,尽可能提高程序执行效率。

        单线程程序为程序员创建了一个幻觉:程序是 “顺序” 执行的。

3.3.2

       顺序一致性内存模型:一种理论参考模型。特点(1)线程任务顺序执行(2)每个操作都是原子操作立即对其它线程可见

3.3.3

        临界区内的代码可以重排序。P34

        同步代码块中的代码可以重排序,不影响程序的结果。

3.4.3

        volatile内存语意:直接操作主存,废弃本地内存

        当写一个volatile变量的时候,JMM会把线程对应的本地内存中的共享变量的值刷新到主内存中。

        当读一个volatile变量的时候,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量的值。

3.5.2

        锁的内存语意:

        当线程释放锁的时候,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。P49

        当线程获取锁的时候,JMM会把该线程对应的本地内存置为无效,并重新从主存中获取。

        synchronized与volatile一样,会让共享变量的缓存无效。在释放锁的时候,将本地内存中的共享变量刷新到主内存中。在获取锁的时候,所有本地的内存将会失效,必须从主存中再次读取。

        A释放锁,A写入主存。B获取锁,B读取主存。相当于A写入了B。

3.6.2

        JMM禁止编译器把final域的写重排序到构造函数之外。P56

        在构造函数执行完毕的时候, final域的变量一定会被初始化,而普通变量就不一定了。所以,另外线程在获取这个对象的这个final修饰的变量的时候,是肯定能够获取到正确的值,而获取普通的变量可能会出现没有初始化的问题。

        编译器会在读final域操作的前面插入一个LoadLoad屏障。P57

        保证读取的变量一定是已经正确初始化后的变量。

3.6.5

        在引用变量被任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确的初始化过了。P59

        在构造函数内部,不能让这个被构造对象的引用被其它线程可见。P59    构造函数{  obj = this ; }//this引用在此“逸出”

        在构造函数返回前,被构造对象的引用不能被其它线程可见,因为此时的final域可能还没有被初始化。

3.8.2

        双重检验锁与延迟初始化的单例模式的问题根源

        

   public static Singleton getInstance() {
        if (instance == null) {
            synchorinzed(lock) {
                if (instance == null)
                    instance = new Singleton();//根源在这一句
            }
        }
        return instance;
    }

        下面重排序过程务必能够手写出来。

       instance = new Singleton(); 这一行代码可以拆分成三句。

       memory = allocate();  //1.分配对象的内存空间

       initInstance(memory); //2.初始化对象

       instance = memroy;    //3.设置instance引用指向这块内存

        上面的代码中,2和3可能会被重排序

        另外一个并发线程B可能拿到的是没有被A线程初始化的对象

        解决办法:(1)禁止指令重排序。所以使用 volatile 关键字修饰instance对象

                          (2)让这个可能会重排序的代码对其它线程不可见。

       使用嵌套类延时加载,如果多个线程同时调用getInstance()方法,那么会执行类初始化。类初始化的时候,会有一把锁,用于同步多个线程对同一个类的初始化。线程A在执行类初始化的时候,可能会出现了重排序,但是这个过程对其它线程是不可见的,所以可以保证正确的初始一个单例对象。

猜你喜欢

转载自blog.csdn.net/yanluandai1985/article/details/82759569