Java并发原理:JDK源码剖析1章 笔记

1.1.1  运行到一半的函数不要强制杀死,调用stop(),destroy()函数虽然可以做到,会造成线程所使用的的资源,比如文件描述符,网络连接等不能正常关闭。合理的运行完毕,干净的释放资源。对于循环运行的线程,要通过线程间的通信机制,通知退出

1.1.2 守护线程,在java中,所有的非守护线程退出后,整个jvm就会退出

1.1.3 在循环中设置关闭标志位,但是如果线程在循环中阻塞,可能无法退出循环。

1.2.1 thread.interrupt()设置中断标志位,但是只有声明了InterruptedException才会抛出这个异常

1.2.2 能够被中断的阻塞称为轻量级阻塞,对应的线程状态是WAITING或者TIMED_WAITING;而像synchronized 这种不能被中断的阻塞称为重量级阻塞,对应的状态是BLOCKED。如果线程处于轻量级阻塞,中断唤醒线程会抛出中断异常。

1.3.1 synchronized给对象加个锁。静态成员函数给A.class加锁,非静态成员函数的时候给对象a加锁

1.3.2 锁的本质:锁就是要实现线程对资源的访问控制,保证同一时间内只允许一个线程访问某一个资源。对象需要维护线程占用状态,占用thredID,thread id list,这些信息在对象头里,有一块数据叫Mark Word会记录锁状态信息

1.6.1 缓存一致性协议,使多个cpu之间不会出现缓存不同步的问题,但是缓存一致性协议对性能损耗很大,要进行各种buffer优化,storebuffer,loadbuffer。

1.6.2 重排序:编译器重排序,cpu指令重排序,cpu内存重排序(造成内存可见性的主因)

       无论什么语言,站在编译器和CPU的角度来说,不管怎么重排序,单线程程序的执行结果不能改变,这就是单线程程序的重排序规则。数据之间没有依赖,重排序之后对结果没影响,就可以重排序。

       对于多线程程序来说,线程之间的数据依赖性太复杂,编译器和CPU没有办法完全理解这种依赖性并据此做出最合理的优化。所以,编译器和CPU只能保证每个线程的as-if-serial语义。线程之间的数据依赖和相互影响,需要编译器和CPU的上层来确定,上层要告知编译器和CPU在多线程场景下什么时候可以重排序,什么时候不能重排序。

       happen-before原则:为了明确定义在多线程场景下什么时候可以重排序,什么时候不可以重排序,引入JMM,java内存模型,就是一套规范。为了描述这个规范,JMM引入了happen-before,使用happen-before描述两个操作之间的内存可见性,如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性。Ahappen before B不代表A一定在B之前执行,但是happen-before只确保如果A在B之前执行,则A的执行结果必须对B可见。定义了内存可见性的约束,也就定义了一系列重排序的约束。happen-before还具有传递性,即若A happen-before B,Bhappen-before C,则A happen-before C

       内存屏障:JMM和happen-before规则的底层实现原理就是使用内存屏障。分为编译器的内存屏障和cpu的内存屏障。编译器的内存屏障只在编译的时候对指令的顺序有作用,编译完成之后,这种内存屏障消失。

1.8 构造函数溢出问题,就是一个对象的构造并不是“原子的”,当一个线程正在构造对象时,另外一个线程却可以读到未构造好的“一半对象”,因为指针的赋值和内存的初始化是不存在依赖关系,有可能重排序。(1)  单线程中的每个操作,happen-before于该线程中任意后续操作。(2)对volatile变量的写,happen-before于后续对这个变量的读。(3)对synchronized的解锁,happen-before于后续对这个锁的加锁。(4)对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的读。四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺

 

        

posted @ 2021-05-10 14:39  gaoxing66  阅读(240)  评论(0编辑  收藏  举报