2021多线程与并发
并发知识点
一、synchronized实现原理
Java中每个对象里都存在一个monitor对象(对象监视器),monitor对象被线程持有时,monitor对象中的count就会进行+1,当线程释放monitor对象时,count会进行-1,用count来表示monitor对象是否被持有。
- Synchronize放在普通方法和类方法上,编译过后会在方法上加个ACC_SYNCHRONIZED标识,方法被执行时,JVM会检查方法上是否有ACC_SYNCHRONIZED标识,如果设置了ACC_SYNCHRONIZED,则会获取锁对象的monitor对象,线程执行完后释放锁对象的monitor对象,在此期间其他线程无法获得锁对象的monitor对象。
- Synchronized放在代码块上,编译过后会有monitor enter和monitor exit指令,
(1) Monitor enter 表示获取锁对象的monitor对象,这时monitor对象中的count会+1,如果monitor已经被其他线程获取,则该线程会阻塞住,直到count=0,再重新尝试获取monitor对象
(2) Monitor exit 表示该线程释放monitor对象,这是monitor对象的count会-1,如果count-1后变为0,其他被阻塞的线程可以重新尝试获取锁对象的monitor对象
二、1.6之后synchronized的优化(锁膨胀,锁优化)
锁可以升级但不能降级:无锁->偏向锁->轻量级锁->重量级锁
偏向锁:针对一个线程而言,线程获得锁之后,该线程再次申请锁的时候不会等待直接放行,会在锁对象头中记录锁的偏向线程ID,以后该线程进入和退出同步块时,不需要进行加锁和解锁操作。
轻量级锁:有两个线程来竞争锁时,偏向锁失效,升级为轻量级锁,会在申请锁的时候进行自旋。
重量级锁:轻量级锁自旋10次后,轻量级锁失效,升级为重量级锁,向os请求资源,进入等待队列,等待cpu调用。
自旋锁比较占用CPU,节省时间(线程数少,线程时间短使用)
重量级锁慢,不占用CPU(线程数多,线程时间长使用)
三、volatile
作用:保证线程可见性、防止指令重排序。
单例模式的懒汉式,双重检查需要加volatile,否则指令重排会出错
线程可见性实现原理:
CPU一般不直接与内存通讯,而是先将系统内存的数据读到内部的高速缓存中再进行操作,但操作完之后并不知道CPU何时将缓存中的数据写回内存,但如果对加了volatile的变量进行写操作,JVM会向CPU发一条lock前缀的指令,会将这个变量的缓存行写回系统内存,但其他CPU中的缓存还是旧的,要实现一致,需要实现缓存一致性协议,当一个CPU将数据写回内存后,其他CPU会通过嗅探总线的数据检查自己缓存的数据是否已过期,如果过期,再次使用该数据时就会先从主内存中读取,如MESI协议:为每个缓存标记一个状态,和主存相比自己是否改过、是否为我独享、是否别的CPU也在读、是否别的CPU改过。
防止指令重排序怎么实现的:
加内存屏障
在每个volatile写操作的前面插入一个Store Store屏障,防止写volatile与后面的写操作重排序。
在每个volatile写操作的后面插入一个Store Load屏障,防止写volatile与后面的读操作重排序。
在每个volatile读操作的后面插入一个Load Load屏障,防止读volatile与后面的读操作重排序。
在每个volatile读操作的后面插入一个Load Store屏障,防止读volatile与后面的写操作重排序。
四、CAS操作
包含三个操作数,内存位置、预期原值、新值;如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置的值更新为新值,否则不做任何操作,大多使用自旋实现无锁操作。
会产生ABA问题:如果一个值原理是A,变成B,又变成了A,也会检查不出变化过,如果需要知道变化过,需要每次更新加版本号解决。
CAS效率高,但比较占用CPU
实现是调用的unsafe类中的compare and set方法,可以直接操作内存,相当于C的指针
有atomic long比synchronization块,因为无锁
Long adder比atomic 快,使用的分段锁,1000个线程会把他们分成4份,分别加锁执行,之后结果相加。
五、synchronization与ReentrantLock
- synchronization是JVM层面的锁,通过monitor对象来实现,实现涉及到锁的升级
ReentrantLock是API层面的锁,通过CAS保证操作的原子性,volatile保证可见性
- Synchronization不需要手动释放锁,执行完之后自动释放
ReentrantLock需要手动释放锁,lock与unlock配合try/finally语句块
- Synchronization是非公平锁
RenntrantLock可以通过构造方法传参数进行选择,默认false非公平、true公平
countdownlatch(length)
countdown方法为递减,latch.await(),一直等待,直到countdown到0
cyclicBarrier栅栏
barrier.await()
等待的线程达到一定数量后,再释放执行,一个复杂操作并发执行,所有都执行完后再执行下一步操作
phaser
流程栅栏
readwritelock
读线程不锁定,写线程锁定
semaphore信号量
限流,构造方法可以传个数量,表示可以同时执行的线程数,
acquare获得锁,release释放锁
exchanger
两个线程互相交换数据,一个线程调用exchange后等待第二个线程调用,都调用后交换数据,继续执行
六、AQS的实现原理
抽象的队列式的同步器,实现是由volatile加双向链表
类中有
volatile修饰的int型的state,同步状态
state是子类使用,实现AQS的想放入什么就放什么
reentrantlock中state是线程重入数量
countdownlatch中是count的数量,减到0执行线程
Note队列,是个双向链表
线程来了之后,先通过CAS竞争锁,如果没有竞争到,将该线程的node节点加入同步队列尾部,判断前驱是否为首节点,是的话CAS尝试获取锁,成功将其设置为首节点,失败进入等待队列等待唤醒,持有锁的线程释放锁的时候会唤醒后继线程。
七、ThreadLocal
相当于在本线程中存放一份变量,线程之间互不影响。
每个线程里都有个map,类型为ThreadLocalMap,
ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。
八、四种引用类型
垃圾回收时会调用finalize方法
强
软:当内存不够用的时候会被回收,可以用在缓存,将大数据缓存到内存,内存不够时可以将其释放。soft
弱:只要遇到垃圾回收就会释放,有一个强引用同时指向一个对象,强引用释放就可以gc,用于容器,weakhashmap、threadlocal
虚:get获取不到指向的值,gc直接回收,但是会往队列里塞个通知,如果使用了堆外内存,可以使用虚引用通知去回收堆外内存
九、线程池
threadpoolexecutor父类是ExcutorService
定义线程池的七个参数
1.corepoolsize 核心线程数
2.maximumpoolsize 最大线程数
3.keepalivetime 生存时间 线程长时间不工作了归还给操作系统,剩核心线程数后不归还
4.timeunit 生存时间的单位
5.blockqueue 阻塞队列
6.线程工厂,自定义创建线程的工厂
7. 拒绝策略,核心为2最大为4,阻塞队列为4,来两个任务核心线程执行,第三个放入阻塞队列,阻塞队列满了后,创建新线程执行,达到4个线程,阻塞队列也满了,执行拒绝策略
提供了四种拒绝策略:1、abort抛异常 2、扔掉不抛异常 3、discardoldest扔掉排队最久的 4、callerruns让调用者去处理
自定义拒绝策略,实现 reectedExecutonHandler 记录未处理的任务,放入kafka、进行处理,或者重试
Disruptor分裂、瓦解
一个线程每秒处理600W订单
速度最快的MQ
性能极高、无锁CAS、单机支持高并发
环形数组buff,使用数组实现的,假如长度为8,当添加到弟12个元素时,是在12%8,余数
环形的结构,0-8,8个满了之后重新覆盖0
生产者生产满之后,如果还需要继续生产,则等待消费者去消费,如果消费者消费了直接覆盖下一个位置的数据