并发总结-面试宝典
2024-01-03 10:27:19 0 举报
AI智能生成
并发总结-面试宝典;是一个全面的资源,旨在帮助面试者在并发编程领域中取得成功。这本宝典包含了核心概念,如进程、线程、锁、同步、异步、并发模型等,以及它们在不同编程语言中的实现。此外,它还提供了面试中可能遇到的典型问题,以及这些问题的解决方案。通过阅读这本宝典,面试者可以全面了解并发编程,提高面试表现。
作者其他创作
大纲/内容
进程线程
区别
进程是资源分配的最小单位,线程是CPU调度的最小单位
举例:应用程序在电脑上执行是一个进程
线程
线程状态
操作系统层面 5种
1、初始状态,创建线程对象,还未与操作系统线程关联
2、可运行状态(就绪),可以被调度
3、运行状态,cpu获取了时间片开始执行
4、阻塞状态,调用了阻塞api,如读写文件
5、终止状态
JAVA层面6种
1、NEW
2、Runnable,包括运行,阻塞和可运行状态,
3、WAITING,join,wait方法
4、TIMED_WAITING, sleep(t)方法
5、BLOCKED 阻塞状态 JAVA中的阻塞状态是指获取不到锁导致线程无法运行的状态
6、TERMINATED
创建方法
1、new Thread
2、 implements Runnable
3、FutureTask
4、线程池
常见方法
1、join
2、sleep
1、Running进入TIMED_WAITING 2、其他线程可以用interrupt打断正在睡眠的线程
3、yield
让,从Running进入Runnable,具体由操作系统调度系统 决定
4、interrupt
打断方法,1、打断sleep,join,wait会清除打断标记,并抛出InterrupedException异常 2、打断park方法会设置打断标记,但是只能生效一次,如果向再次park,必须重新清除打断标记Thread.interrupted(判断并清除打断标记)
5、interrupted
判断是否打断,并清除打断标记,打断正常代码,不会立即中断代码,需要通过判断打断标记来决定是否中断
线程池
参数
核心线程数、最大线程数、keepAliveTime(非核心线程的闲置超时时间)、unit时间单位、workQueue阻塞队列、threadFactory线程工厂(可以给线程起名字)、handler拒绝策略
拒绝策略
AbortPolicy,抛出拒绝异常,默认策略
CallerRunPolicy,让调用者运行任务
DiscardPolicy,放弃本次任务
DiscardOldestpolicy,放弃队列中最早的任务,本任务取而代之
自定义:实现 RejectedExecutionHandler 接口来创建自定义的拒绝策略
执行步骤
1、如果线程池中线程数量小于核心线程数,那么新建一个线程
2、如果核心线程数满了,那么进入阻塞队列中
3、如果阻塞队列也满了,看有没有非核心线程(最大线程 - 核心线程),如果有,创建一个线程执行
4、如果线程也满了,那么使用拒绝策略
设置多少线程合适
CPU密集型
采用CPU核数+1
IO密集型
线程数 = 核数 * cpu利用率 * 总时间/cpu计算时间
压测根据实际情况调整
线程池分类
固定:newFixedThreadPool
核心线程 = 最大线程,没有应急线程,无限制阻塞队列LinkedBlockingQueue
适用线程数已知,相对耗时的稳定任务
缓存:newCachedThreadPool
核心线程为0,全部是应急线程,最大线程数是 Integer.MAX_VALUE,队列是同步队列SynchronousQueue,队列不放任何元素,没有容量,没有线程来去是放不进去元素的
优缺点
适用于短生命周期/ 非高并发的异步任务
不适用于长时间运行的任务
内存占用问题: 由于线程数没有上限,如果短时间内提交了大量的任务,可能导致大量线程同时存在
单一:newSingleThreadPool
核心线程数 = 最大线程数 = 1,LinkedBlockingQueue
按照任务的提交顺序执行任务,并且同一时间只有一个任务在执行。避免了线程的创建和销毁开销,同时保持了任务的有序执行。
定时任务:newScheduledThreadPool
DelayedWorkQueue,,无界阻塞队列
定时任务场景
并发导致什么问题
可见性
原子性
有序性
怎么解决并发问题
互斥同步
synchronized
what
又叫对象锁,锁的必须是对象。
可重入
非公平
how:怎么用
1、普通方法,对当前实例对象this加锁
2、代码块,指定一个加锁的对象,给对象加锁
3、静态方法,对当前类的Class对象加锁
why: 为什么能实现互斥(原理)
代码示例
用synchronize 反编译后的代码
子主题
结构
对象在jvm 里按三部分存储:对象头,实例及对齐填充。
其中对象头里会存储对象的HashCode,分代年龄和锁标志位信息
monitor对象,管程
1、组成,owner,entrylist,waitset
2、java会为每一个对象关联一个monitor锁对象,当用synchronized关键字给对象上锁后,对象头中的markword就会替换为monitor的地址,monitor中的owner被替换为获取锁的线程,当其他线程来获取锁时,发现owner线程不是自己,会进入entrylist进行等待,waitset是指获取到锁,但是条件不满足进入waitset等待(notiry,wait)
3、字节码层面,monitorenter,monitorexit
图例:
why: 为什么非公平
为什么
公平与非公平的区别就在于 新来的线程是直接排队,还是先去获取锁🔐
好处
减少唤醒的开销,吞吐量更大
这是因为唤醒线程2会有很大的开销,因为程序的执行大部分都很快,很可能在唤醒线程2之前,线程5就已经执行完毕了,所以按照非公平策略的逻辑,这里会让线程5先获取到锁,相比于等待线程2唤醒的漫长过程,直接执行线程5效率会更高,这是一个双赢的局面。
缺点
线程饥饿
线程5执行完之后来了6 ,导致2 一直无法得到锁
how: 锁优化
为什么要优化
怎么优化
偏向锁
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,只有第一次通过CAS将对象头中的thread设置为当前线程。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁。只需要简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果成功,表示线程已经获取到了锁。
轻量级锁
过程
1、当有线程竞争,交替获取锁时,从偏向锁升级为轻量级锁。
2、轻量级锁,线程会在栈中创建一个锁记录(lock record 地址,对象引用),线程会让对象引用指向锁的对象,同时让lock record地址和对象头中的markword进行CAS原子交换,如果交换成功,说明加锁成功,如果失败可能锁重入,也可能时有竞争
3、轻量级锁重入,当同一线程再次加锁时,会在栈中再次创建一个锁记录,lock record地址为null,作为锁重入计数,退出代码块时,如果所记录中lock record地址为null表示锁重入,重置锁记录,计数-1,最终交换对象头,解锁
好处
对在大多数情况下同步块并不会有竞争出现提出的一种优化。它可以减少重量级锁对线程的阻塞带来的线程开销。从而提高并发性能。
JDK 1.6之后引入的轻量级锁
重量级锁
当获取轻量级锁中发生竞争,锁膨胀为重量级锁,将对象头中lockrecord 地址替换为minitor锁对象地址,monitor中owner线程为当前线程,竞争锁的线程进入entrylist阻塞队列
自旋优化
由于线程进入阻塞队列会发生上下文切换,影响效率,所以进入阻塞队列前会自旋获取锁,如果之前线程已经释放锁,那么便不再进入阻塞队列,直接获取锁
锁膨胀过程
子主题
AQS
(AbstractQueuedSynchronizer)抽象队列同步器,是java.util.concurrent包下大多数同步器实现的基础
ReentrantLock
基于AQS,可重入,可公平可非公平,默认非公平,可设置锁超时时间
原理
AQS 中维护了一个 FIFO 队列 和 volatile 修饰的status,通过CAS修改status 。
图示
要点
1、获取和释放
1、当第一个线程获取锁时,将state替换为1,表示加锁成功,当有其他线程再次获取锁时,尝试获取锁失败,开始尝试加入阻塞队列,addWaiter,进入死循环,再次尝试失败后尝试进入阻塞队列并park当前线程,park前需要保证阻塞队列前一个节点waitStatus 为-1(正常状态为0),用来唤醒后继节点,当前驱节点状态为-1时,加入阻塞队列并park当前线程,队列头节点默认为null称为dummy哑元或哨兵
2、释放:队列头节点为-1,唤醒unpark下一个节点,并且把当前头节点移除,下一个节点置为null
2、释放:队列头节点为-1,唤醒unpark下一个节点,并且把当前头节点移除,下一个节点置为null
2、可重入
判断如果是当前线程获取锁,那么state + 1,表示锁重入
3、可打断
1、不可打断模式,当有其他线程打断时,会继续向下运行,return Thread.interrupted, 清除打断标记,会返回aquireQueue方法,只是做了一个打断标记,继续死循环获取锁,直到获得锁以后,返回上一层,才通过打断标记进行自我打断
2、可打断模式,park时,如果有其他线程打断,直接抛出打断异常
4、公平,非公平
1、非公平锁,先去尝试获取锁,失败后再加入阻塞队列
2、公平锁,先判断阻塞队列有没有前驱节点,有就加入队列
5、Condition
每一个条件变量都关联一个等待队列,实现类就是ConditionObject
每一个条件变量都关联一个等待队列,实现类就是ConditionObject
await
当线程获取锁后,不满足条件后,调用await,进入等待队列中,Node状态为-2
signa
正在执行的线程满足一定条件后,唤醒等待队列中的线程,通过transferForSignal将线程从等待队列中加入阻塞队列中开始去竞争锁
与wait/notify 的区别
读写锁
ReentrantReadWriteLock
ReentrantReadWriteLock
1、不可以升级获取锁,先获取了读锁,不能再获取写锁,先获取了写锁,可以再获取读锁
state高16位表示读锁,低16位表示写锁,读锁进入阻塞队列会标明SHARED,多个读锁线程会同时被唤醒,写锁进入阻塞队列标明位EXCLUSIVE独占锁
唤醒锁时,先把node节点状态从-1改为0,表示加锁,让其他线程先不要修改
CyclicBarrier
拦截线程数达到一定计数时执行action
可重复使用
不阻塞主线程
CountDownLatch
构造函数用来初始化计数器,countdown计数减一,await等待计数为0时开始执行
不可重复使用
阻塞主线程
Semaphore
信号量,可以做流量控制,用来限制同时访问共享资源的线程上限
volatile
1、保证可见性和有序性
其中有序性是针对单线程内指令重排,并不能保证多线程下的原子性
2、原理是通过读写屏障实现
1、写屏障,写指令之后加入写屏障,保证在此之前的写操作都已经同步到主存,同时确保写屏障之前的代码不会排在之后
2、读屏障,读指令之前加入写屏障,保证在此之后读取的数据都是从主存中获取的,同时确保读屏障之后的代码不会排在之前
非阻塞同步
Atomic 类
CAS
1、swap and compare,用原有值和预期值不断的去比较交换
2、原理,底层实现是通过 lock compxche指令,锁住总线,保证交换的原子性
3、CAS实现离不开volatile,要保证变量的可见性才能保证获取到变量的最新值
4、特点
可以实现无锁,无阻塞并发,适用于线程少,多核CPU的场景下
ABA问题
通过版本号解决,AutomicStampedRefrence
无同步方案
0 条评论
下一页