并发--线程和锁
2019-03-31 本文已影响77人
简书徐小耳
线程调度
协同式调度
- 1.一个线程执行完毕之后再通知其他线程执行
抢占式调度(JAVA使用的是这种方式)
- 1.os为每个系统分配执行时间
- 2.线程的切换不是由线程本身来决定
- 3.Thread.yield可以让出执行时间,但是无法获取执行时间
- 4.优先级越高,os分配的执行时间越多。
os调度cpu有全局队列和局部队列
- 全局队列:当有多核的时候,os通过看哪个cpu空闲就让这个cpu执行任务(线程),cpu利用率很高,高速缓存的命中率很低。
- 局部队列:每个核心有一个队列,好处就是每个core的高速缓存命中率很高,但是cpu空闲率很高
对于sun jdk 都是一个java线程对应一个内核线程(轻量级进程)
线程调度
协同式调度
- 1.一个线程执行完毕之后再通知其他线程执行
抢占式调度(JAVA使用的是这种方式)
- 1.os为每个系统分配执行时间
- 2.线程的切换不是由线程本身来决定
- 3.Thread.yield可以让出执行时间,但是无法获取执行时间
- 4.优先级越高,os分配的执行时间越多。
os调度cpu有全局队列和局部队列
- 全局队列:当有多核的时候,os通过看哪个cpu空闲就让这个cpu执行任务(线程),cpu利用率很高,高速缓存的命中率很低。
- 局部队列:每个核心有一个队列,好处就是每个core的高速缓存命中率很高,但是cpu空闲率很高
对于sun jdk 都是一个java线程对应一个内核线程(轻量级进程)
Synchronized的优化
synchronized的锁策略
Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率
自旋锁和自适应自旋
- 1.自旋是类似于CAS。
- 2.自适应的意思是超过一定尝试次数就开始变为重量级锁。
- 3.这边自旋锁获取的锁第一次锁是轻量级锁,后续轻量级锁后续会变为重量级锁。
锁消除
- 当JIT编译器运行时候检测到一些代码中不存在共享数据竞争的时候,则消除锁
锁粗化
- 当一段代码中出现过多的synchronized,而其也不必要,则会粗化为一个synchronize,避免了不停的重复获取和释放锁。
轻量级锁
- 不是替代重量级锁,是为了替代没有多线程竞争的前提下减少传统的重量级锁使用操作系统互斥量(monitor)产生的性能消耗
- 通过CAS避免了互斥量的开销
偏向锁
- 如果轻量级锁是在无竞争的情况下通过CAS获取锁,那么偏向锁就是则 连CAS都省去了。
- 如果该锁被第一个线程获取,后续执行 过程没有其他线程获取则持有偏向锁永远不需要同步
- 第一个线程获取锁的时候则把标志位设置为01,同时使用CAS操作把获取到这个锁的ID记录在对象的MarkWord之中。
- 如果CAS成功则持有偏向锁的线程以后每次进入这个锁的相关同步快的时候,JVM不会再进行任何同步(Locking,UnLocking以及对Mark Word的update)
- 当有其他线程去尝试获取这个锁的时候,偏向锁结束。根据锁对象目前是否处于被锁定的状态 ,如果不处于锁定则是恢复到未锁定状态但是不可偏向(markword里面不存储该线程的ID),如果处于锁定则变为偏向锁。
synchronized几种锁转化方式
- 初始状态下虚拟机会在当前线程的栈帧中建立一个锁记录的空间
- 该锁空间用于存储锁对象目前的mark word拷贝(官方在拷贝前面加了Displaced)
- JVM通过CAS(自旋锁)尝试将对象的markWord更新为指向lock record的指针。
- 更新成功则这个线程获取了该对象的锁了(把对象的markword指向当前栈帧的LockRecord,也就是指向了当前线程,因为栈帧是和线程绑定的)
- 获取锁成功则修改markword(不是displaced)的锁标志位改为00(轻量级锁定)。
- 更新失败,JVM会检测对象的markWord是否指向当前线程栈帧,如果是则可以直接进入同步块继续执行,否则说明被别人抢先了。
- 这个时候就会变为重量级锁,锁标记为10,后面等待的线程则根据自适应条件进行释放阻塞还是不停的CAS.
- 轻量级锁解锁也是CAS,如果对象的markWord仍然执行线程的锁记录,那就用CAS操作把当前对象的markword和线程中赋值的displacedMarkWord替换。
- 替换成功,则整个同步过程就完成了,如果失败了说明其他线程尝试过获取该锁,则需要在释放锁的同时唤醒被挂起的线程。
对象头
mark word
指向方法区对象类型数据的指针(如果是数组对象还会额外用于存储数组长度)
JAVA对象包含的三部分(hotspot)
对象头
Mark Word
- 存储对象自身的运行时候数据(hashcode,GCAge)
- 上述这部分数据的长度在32位和64位分别32个和64bits
- markword 是一个非固定的数据结构,以便在极小的空间存储更多的信息
- markword中32个bits空间中的25Bits用于存储对象hashcode,4bit用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0
- 不同的锁状态倒数2和3的bit标志不同
类型指针
实例数据
- 存储对象真正的有效信息(代码中的各类型的字段内容)
- 无论是从父类继承来的字段还是子类中的定义
对齐填充
- 仅仅是占位符的作用
- 因为JVM要求对象的起始地址必须是8个字节的整数倍,而对象头是8的整数倍,如果实例数据没有对齐,则需要对齐填充。
synchronized的原理
自旋锁和自适应自旋
- 1.自旋是类似于CAS。
- 2.自适应的意思是超过一定尝试次数就开始变为重量级锁。
- 3.这边自旋锁获取的锁第一次锁是轻量级锁,后续轻量级锁后续会变为重量级锁。
锁消除
- 当JIT编译器运行时候检测到一些代码中不存在共享数据竞争的时候,则消除锁
锁粗化
- 当一段代码中出现过多的synchronized,而其也不必要,则会粗化为一个synchronize,避免了不停的重复获取和释放锁。
轻量级锁
- 不是替代重量级锁,是为了替代没有多线程竞争的前提下减少传统的重量级锁使用操作系统互斥量(monitor)产生的性能消耗
- 通过CAS避免了互斥量的开销
偏向锁
- 如果轻量级锁是在无竞争的情况下通过CAS获取锁,那么偏向锁就是则 连CAS都省去了。
- 如果该锁被第一个线程获取,后续执行 过程没有其他线程获取则持有偏向锁永远不需要同步
- 第一个线程获取锁的时候则把标志位设置为01,同时使用CAS操作把获取到这个锁的ID记录在对象的MarkWord之中。
- 如果CAS成功则持有偏向锁的线程以后每次进入这个锁的相关同步快的时候,JVM不会再进行任何同步(Locking,UnLocking以及对Mark Word的update)
- 当有其他线程去尝试获取这个锁的时候,偏向锁结束。根据锁对象目前是否处于被锁定的状态 ,如果不处于锁定则是恢复到未锁定状态但是不可偏向(markword里面不存储该线程的ID),如果处于锁定则变为轻量级锁
synchronized几种锁转化方式
- 初始状态下虚拟机会在当前线程的栈帧中建立一个锁记录的空间
- 该锁空间用于存储锁对象目前的mark word拷贝(官方在拷贝前面加了Displaced)
- JVM通过CAS(自旋锁)尝试将对象的markWord更新为指向lock record的指针。
- 更新成功则这个线程获取了该对象的锁了(把对象的markword指向当前栈帧的LockRecord,也就是指向了当前线程,因为栈帧是和线程绑定的)
- 获取锁成功则修改markword(不是displaced)的锁标志位改为00(轻量级锁定)。
- 更新失败,JVM会检测对象的markWord是否指向当前线程栈帧,如果是则可以直接进入同步块继续执行,否则说明被别人抢先了。
- 这个时候就会变为重量级锁,锁标记为10,后面等待的线程则根据自适应条件进行释放阻塞还是不停的CAS.
- 轻量级锁解锁也是CAS,如果对象的markWord仍然执行线程的锁记录,那就用CAS操作把当前对象的markword和线程中赋值的displacedMarkWord替换。
- 替换成功,则整个同步过程就完成了,如果失败了说明其他线程尝试过获取该锁,则需要在释放锁的同时唤醒被挂起的线程。
对象头
mark word
指向方法区对象类型数据的指针(如果是数组对象还会额外用于存储数组长度)
JAVA对象包含的三部分(hotspot)
对象头
Mark Word
- 存储对象自身的运行时候数据(hashcode,GCAge)
- 上述这部分数据的长度在32位和64位分别32个和64bits
- markword 是一个非固定的数据结构,以便在极小的空间存储更多的信息
- markword中32个bits空间中的25Bits用于存储对象hashcode,4bit用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0
- 不同的锁状态倒数2和3的bit标志不同
类型指针
实例数据
- 存储对象真正的有效信息(代码中的各类型的字段内容)
- 无论是从父类继承来的字段还是子类中的定义
对齐填充
- 仅仅是占位符的作用
- 因为JVM要求对象的起始地址必须是8个字节的整数倍,而对象头是8的整数倍,如果实例数据没有对齐,则需要对齐填充。