导图社区 多线程与高并发-web-1651074353400
多线程与高并发-web-1651074353400的思维导图,内容有线程基础、线程的打断、线程的结束、并发编程的三大特性、sychronized、高并发、源码、lock底层~
编辑于2023-03-05 10:00:55F
线程基础
进程
一个程序在内存中每一次运行都是进程,进程是操作系统进行资源分配的基本单位
线程
进程内部,调度执行的基本单位,是动态的,进程是静态的
线程的状态
线程的打断
线程打断的三种方法
interrupt 设计打断标记位
isinterrupt 判断
static interupted 判断,并且重置(当前对象return currentThread().isInterrupted(true);)
volatile 类型的变量控制结束,无法精准控制,
interrupt后,sleep,join,wait抛异常,catch之后复位
锁池,等待池 notify,wait
设置标记位置
线程的结束
Stop 已经被弃用,不建议用
过于粗暴,容易产生数据不一致
suspend和resume 被弃用,同上
用volatile或者打断
并发编程的三大特性
可见性
volatile,修饰的内存每次都从主存读,即一有改变立马可见
线程可见
通过Lock前缀指令+MESI缓存一致性协议,lock前缀保证及时刷进内存,协议保证金】本地缓存及时修改
禁止重排序
synchronized锁竞争会触发线程内存数据同步主内存
保证可见,和原子
缓存行 64总结 程序的局部性原理:可以提高效率 充分发挥总线 CPU针脚等一次性读取更多数据的能力。空间局部性原理:空间相关的问题 例如:一次性读取数据相邻的数据。时间局部性原理:指令相关的问题缓存行:每行大小为64个字节
缓存一致性协议,通过填充缓存行,提高效率(两颗cpu之间)
-XX:-RestrictContended,启动配置加这个,在变量加上@Contended注解(仅在1.8起作用)
不同cpu不一样,MESI英特尔设计
有序性
存在条件:不影响单线程的最终一致性
半初始化状态
new申请内存,成员变量初始化
特殊调用构造方法
建立连接
return
指令重排:提升效率
编译器优化重排序
在不改变单线程结果的情况下
指令级并行重排序
如果数据不存在依赖,处理器可以改变机器指令的执行顺序
内存系统重排序
处理器使用写缓冲区临时保存写入数据
原子性
Java中原子操作:上锁解锁
上锁的本质
并发——>序列化
上锁保证了可见和原子性,锁竞争会触发线程内存数据同步主内存
线程同步
monitor管程(锁)
临界区
时间长,语句多,锁的粒度比较粗
sychronized
用户态与内核态,JDK早期(1.6之前)叫重量锁,,操作系统为了保证安全,不混乱,把CPU分成了特权模式和用户模式,也就是内核态和用户态
1.6之后,锁升级
偏向锁
锁对象的markword中,记录线程指针
自旋锁
线程栈中(lock record)指针
没有开启偏向锁,默认为轻量级级锁
重量级锁
JVM 写的C++对象Objectmoniter,需要请求操作系统拿锁
竞争加剧:有线程超过10次自旋,-XX:PreBlockSpin,或者自旋线程数超过CPU核数的一半,1.6之后,加入自适应自旋Adapative Self Spinning, IVM自己控制
可重入锁
偏向锁,轻量级锁,每锁一次 LR+1(第一次LR会备份markword,存储hashcode,hashcode位置被占用了,忘了看课程)
重量级锁,放在Objectmoniter
在多线程情况下,偏向锁一定会锁撤销会消耗资源,所以默认不开启偏向锁
自旋锁什么时候升级为重量级锁?
为什么有自旋锁还需要重量级锁?
自旋是消耗CPU资源的,如果锁的时间长,或者自旋线程多,CPU会被大量消耗重量级锁有等待队列,所有拿不到锁的进入等待队列,不需要消耗CPU资源
偏向锁是否一定比自旋锁效率高?
不一定,在明确知道会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销,这时候直接使用自旋锁JVM启动过程,会有很多线程竞争(明确),所以默认情况启动时不打开偏向锁,过一段儿时间再打开 (默认4秒) 工具:JOL =Java Object Layout
子主题默认情况下,出现异常锁会被释放
高并发
AtomicLong LongAdder(分段锁)
ReentrantLock
trylock
lockinterupptibly
公平
底层是CAS
不同的等待队列 Condition
lock.newCondition()
await()、signal()、signalAll()
Cyclic Barrier
ReadWirteLock
stampedLock
https://blog.csdn.net/qq_34978129/article/details/106996598
读是共享的,写是排他的
Semaphore(可允许任意个线程同时执行)
acquire 信号灯
release
exchanger
Locksupport
park,unpark(线程名字),不需要再syn or lock 里面就可以实现阻塞和唤醒,而且可以指定哪个唤醒,wait方法和notify方法 是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)
unpark可以在 park之前使用
CountDownLatch
源码
ReentrentLock源码
VarHandle -> 1:普通属性原子操作2:比反射快,直接操纵二进制码
lock底层
AQS+volatile
AQS
入站不需要CAS,因为设置 head 节点不需要用 CAS,原因是设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证,只需要把 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用即可。
https://blog.csdn.net/weixin_45596022/article/details/113817683
volatile int state 0代表被占用,获得变成1,可重入就+1
子主题
Threadlocal
每个线程(Thread)内部维护了一个map集合,key为TheadLocal对象,value为set进去的东西
java的四种引用
强引用
有引用,一定不会回收
new
软引用
超出内存(堆内存),才能回收
SoftReference
弱引用
引用消失,回收
WeakReference key->Threadlocal(需要remove ,这个map是静态内部类ThreadLocalMap)
虚引用
回收堆外内存,通过检查Queue
容器
ConcurrentHashMap
底层红黑树,插入效率并不高,但读的效率一流
HashTable直接上锁 Hashmap不上锁 Collections.synchronizedMap锁优化
Vector->Queue
Queue的poll取东西都是无锁的CAS操作 抛出异常 返回特殊值 插入 add(e) offer(e) 移除 remove() poll() 检查 element() peek()
BlockingQueue 实现了put take阻塞方法(满了或者拿完了阻塞)link
synchronizedQueue 一取一拿,容量为零,take put
transferQueue
ConcurrentSkipListMap
底层树+CAS过于复杂,所以使用跳表实现,树的查找O(logn),跳表也是
由于Vector读写都加锁,copyonwritearraylist读不加锁
改变数组时候加锁,复制一个新的改变过的数组(底层是数组,不可变,所以要复制而不是直接加)
面试题
单核CPU设定多线程是否有意义?
工作线程数是不是设置的越大越好
工作线程数(线程池中线程数量)设多少合适?
CPU数量 期望利用率 (1-w/c)
volatile如何实现可见性和禁止指令重排
syn和lock的区别
线程类
Callable
Futuretask.get接收 Callable的返回值
对象在内存中的存储布局(对于hotspot)
markword 标志位 8字节
锁信息
GC信息
identity hashcode
类型指针 4字节(不压缩8字节),32g内存压缩指针失效
实例数据instanc data
填充,或者对齐padding(8字节的倍数)
指针
类指针
普通对象指针
对象头
数组长度 4字节
String s=“lkadhsakd”
对象怎么定位
直接指针
间接指针 类型和实例混合
对象怎么分配,栈,堆,tlab
首先是对象只在当前线程使用,不存在共享问题。其次对象不能太大, 毕竟要分配在栈内存上。在JDK1.6之后就已经默认开启了站上分配。
标量替换
逃逸分析
通过内存屏障(Memory Barrier),无法指令重排,写的时候直接刷新进内存,读的时候冲内存读(底层是lock,lock也是内存屏障)
https://blog.csdn.net/weixin_45007916/article/details/108076954
JVM要求实现的四种屏障
loadload
storestore
确保前面的写操作的可见性(刷到内存)先于后面
loadstore
storeload
如果面试官问你 JVM,额外回答逃逸分析技术会让你加分! https://blog.csdn.net/chenlixiao007/article/details/118772307
锁消除
thread local allocation buffer TLAB
锁
悲观锁
乐观锁
CAS 操作(比较和交换)
ABA问题 对于引用加 版本 version(数值型,布尔类型AtomicMarkableReference 只能缓解ABA)
两种锁的适用场景
实战中用synchronized(内部优化了很多,效率不错)
多核情况下必须保证CAS的原子性,怕另一个CAS打断(单核不用)
由native修饰,底层由 C++写,用到了汇编语言(CPU)级别的 CAS 加锁(判断是否为多核),最底层还是 lock锁CAS
双重锁,为什么加valutile,半初始化
自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。偏向锁由于有锁撤销的过程revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高。还不如直接使用轻量级锁。
happens-before
lock unlocl
依赖
volatile的写 读
join start
Queue和List的区别
对线程友好的API offer peek pollBlockingQueue put take ->阻塞 可以很自然的实现生产者消费者底层是park
浮动主题
浮动主题