学习笔记
计算机组成
- CPU (Central Processing Unit) 中央处理器
- 存储器
- 输入输出设备
计算机工作原理:首先在控制器输入命令控制下,通过输入设备输入指令,保存在存储器中。开始运算时,从内存中读取指令,通过控制器对指令进行译码,并根据指令的操作向存储器和运算器发出存储、取数和运算命令,把结果存放存储器内。最后在控制器控制下,通过输出设备输出。
CPU
CPU由控制单元(Control Unit)、运算单元(Arithmetic/Logic Unit)和存储单元(Memory Unit)三部分组成。
上下文切换(Context Switch) : 一个工作的线程被另一个线程占用了处理器使 用权的过程。
- 一个线程被剥夺处理器的使用权而被暂停运行,称为“切出”。
- 一个线程被选中占用处理器开始或继续运行,称为“切入”。
- 在这种切入切出的过程中,操作系统需要保存和恢复相应的进度信息,这个进度信息就是“上下文”。
线程
线程分为用户级线程(User Thread,UT) 和内核级线程(Kernel-level Thread,KLT)。
用户线程和内核线程对应关系:
- 一对一模型(内核级线程模型)
- 多对一模型(用户级线程模型)
- 多对多模型(两级线程模型)
操作系统一般只实现到一对一模型。
Java使用的是一对一线程模型,所以它的一个线程对应一个内核线程,调度完全交给操作系统来处理。
锁
重量级锁,通过操作系统来管理线程
轻量级锁,通过用户线程管理线程或者是。。。
轻量级锁需要消耗CPU资源,重量级锁不需要消耗CPU资源,通过队列来实现。
偏向锁、自旋锁都是用户空间完成,重量级锁需要向内核申请
锁升级:没有锁-->偏向锁-->自旋锁-->重量级锁
偏向锁:偏向第一个线程的锁,并不是真的锁
偏向锁默认情况有4秒时延,因为jvm虚拟机自己有一些默认启动的线程,里面好多sync代码,这些代码启动时肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级操作,效率低。
Synchronized
CAS最终实现 lock cmpxchg指令
ABA: 加版本号
AtomicInteger
一个Object占多少字节 (16)
Object o = new Object(); 对象头(markword) 8byte、类指针(class pointer) 4byte、实例数据(instance data) 0byte 和对齐(padding)
在64位系统8字节对齐,不能被8整除的补。所以8+4+0+4 = 16
markword里面记录了锁信息、hashcode和垃圾回收信息。
JVM调优:设定启动参数
缓存行(Cache line)
The minimum amount of cache which can be loaded or stored to memory.
X86 CPUs -64 Bytes
CPU访问内存时,首先查询cache是否已缓存该数据。如果有,直接返回;如果不存在,则需要把数据从内存载入cache,最后返回给处理器。Cache之所以有效,是因为程序对内存的访问存在一种概率上的局部特征:Spatial Locality(对于刚被访问的数据,其相邻的数据在将来被访问的概率高)和 Temporal Locality(对于刚被访问的数据,其本身在将来被访问的概率高)。
当CPU执行运算的时候,它先去L1查找所需数据,如果没有再去L2,然后是L3,如果缓存中都没有,则去主内存中拿。然后按照L3->L2->L1放入缓存,最后返回给处理器。
伪共享(False Sharing):当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能。
伪共享解决方案:字节填充(增大元素的间隔,使得不同线程存取的元素位于不同的cache line上, 典型的空间换时间)也可以在每个线程中创建对应元素的本地拷贝,结束后再写回全局数组。
Double Check Lock 双重检查锁
if(instance == null) {
synchronized(this.class) {
if(instance == null) {
// new
}
}
}
DCL单例需要volatile来保证不能乱序。(因为new一个对象汇编语言实现过程中可能在向内存申请空间后,初始化操作和建立关联操作发生指令重排序,导致使用了半初始化的对象)