2021多线程与并发

并发知识点

一、synchronized实现原理

Java中每个对象里都存在一个monitor对象(对象监视器),monitor对象被线程持有时,monitor对象中的count就会进行+1,当线程释放monitor对象时,count会进行-1,用count来表示monitor对象是否被持有。

  1. Synchronize放在普通方法和类方法上,编译过后会在方法上加个ACC_SYNCHRONIZED标识,方法被执行时,JVM会检查方法上是否有ACC_SYNCHRONIZED标识,如果设置了ACC_SYNCHRONIZED,则会获取锁对象的monitor对象,线程执行完后释放锁对象的monitor对象,在此期间其他线程无法获得锁对象的monitor对象。
  2. Synchronized放在代码块上,编译过后会有monitor entermonitor 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 longsynchronization块,因为无锁

Long adderatomic 快,使用的分段锁,1000个线程会把他们分成4份,分别加锁执行,之后结果相加。

五、synchronizationReentrantLock

  1. synchronizationJVM层面的锁,通过monitor对象来实现,实现涉及到锁的升级

   ReentrantLockAPI层面的锁,通过CAS保证操作的原子性,volatile保证可见性

  1. Synchronization不需要手动释放锁,执行完之后自动释放

   ReentrantLock需要手动释放锁,lockunlock配合try/finally语句块

  1. Synchronization是非公平锁

   RenntrantLock可以通过构造方法传参数进行选择,默认false非公平、true公平

 

 

countdownlatchlength

countdown方法为递减,latch.await(),一直等待,直到countdown0

 

 

cyclicBarrier栅栏

barrier.await()

等待的线程达到一定数量后,再释放执行,一个复杂操作并发执行,所有都执行完后再执行下一步操作

phaser

流程栅栏

readwritelock

读线程不锁定,线程锁定

semaphore信号量

限流,构造方法可以传个数量,表示可以同时执行的线程数,

acquare获得锁,release释放锁

exchanger

两个线程互相交换数据,一个线程调用exchange后等待第二个线程调用,都调用后交换数据,继续执行

 

六、AQS的实现原理

抽象的队列式的同步器,实现是由volatile加双向链表

类中有

volatile修饰的int型的state,同步状态

state是子类使用,实现AQS的想放入什么就放什么

reentrantlockstate是线程重入数量

countdownlatch中是count的数量,减到0执行线程

Note队列,是个双向链表

线程来了之后,先通过CAS竞争锁,如果没有竞争到,将该线程的node节点加入同步队列尾部,判断前驱是否为首节点,是的话CAS尝试获取锁,成功将其设置为首节点,失败进入等待队列等待唤醒,持有锁的线程释放锁的时候会唤醒后继线程。

七、ThreadLocal

相当于在本线程中存放一份变量,线程之间互不影响。

每个线程里都有个map,类型为ThreadLocalMap

ThreadLocalMap中的keyThreadLocal的弱引用。当一个线程调用ThreadLocalset方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。

八、四种引用类型

垃圾回收时会调用finalize方法

软:当内存不够用的时候会被回收,可以用在缓存,将大数据缓存到内存,内存不够时可以将其释放。soft

弱:只要遇到垃圾回收就会释放,有一个强引用同时指向一个对象,强引用释放就可以gc,用于容器,weakhashmapthreadlocal

虚:get获取不到指向的值,gc直接回收,但是会往队列里塞个通知,如果使用了堆外内存,可以使用虚引用通知去回收堆外内存

 

九、线程池

threadpoolexecutor父类是ExcutorService

定义线程池的七个参数

1.corepoolsize 核心线程数

2.maximumpoolsize 最大线程数

3.keepalivetime 生存时间 线程长时间不工作了归还给操作系统,剩核心线程数后不归还

4.timeunit 生存时间的单位

5.blockqueue 阻塞队列

6.线程工厂,自定义创建线程的工厂

7. 拒绝策略,核心为2最大为4,阻塞队列为4,来两个任务核心线程执行,第三个放入阻塞队列,阻塞队列满了后,创建新线程执行,达到4个线程,阻塞队列也满了,执行拒绝策略

提供了四种拒绝策略:1abort抛异常 2、扔掉不抛异常 3discardoldest扔掉排队最久的 4callerruns让调用者去处理

自定义拒绝策略,实现 reectedExecutonHandler 记录未处理的任务,放入kafka、进行处理,或者重试

 

Disruptor分裂、瓦解

一个线程每秒处理600W订单

速度最快的MQ

性能极高、无锁CAS、单机支持高并发

环形数组buff,使用数组实现的,假如长度为8,当添加到弟12个元素时,是在12%8,余数

环形的结构,0-88个满了之后重新覆盖0

生产者生产满之后,如果还需要继续生产,则等待消费者去消费,如果消费者消费了直接覆盖下一个位置的数据

posted @ 2021-09-11 20:33  乔儿的终极小迷弟  阅读(51)  评论(0编辑  收藏  举报