并发编程
2021-03-31 10:55:00 27 举报
AI智能生成
并发整体梳理
作者其他创作
大纲/内容
并发, 并行
并发
逻辑架构 程序交替的跑在不同的时间片上,
时间片执行完后, 发生上下文切换
时间片执行完后, 发生上下文切换
并行
物理架构 只可能出现在多核
线程池
Executors.newCachedThreadPool()
实现方式
Executors.newFixedThreadPool()
Executors.newSingleThreadExecutor()
线程复用
线程复用: 我们去提交任务的时候, 去创建线程, 创建worker, 然后 启动线程, 再去获取任务, 执行完成之后,
在finally里面有一个processWorkerExit, 它是一个无论如何都会执行的代码, 里面会再去调用addWorker
在finally里面有一个processWorkerExit, 它是一个无论如何都会执行的代码, 里面会再去调用addWorker
拒绝策略
丢弃任务并抛出RejectedExecutionException异常
线程池默认使用
线程池默认使用
丢弃任务,但是不抛出异常。
丢弃队列最前面的任务,然后重新提交被拒绝的任务
由调用线程(提交任务的线程)处理该任务
补充
等待唤醒机制
基于monitor实现, object.warit/notify
基于线程的, LockSupport.park/unpark
基于线程的, LockSupport.park/unpark
线程中断机制
interrupt: 仅仅设置一个中断标志位, 不停止线程
Fork join
分治思想, 一个线程, 对应一个队列,
把任务往队列(双端队列,LIFO后进先出)里面放
把任务往队列(双端队列,LIFO后进先出)里面放
线程
线程, 进程
进程
一个程序就是一个进程, 它最少由一个线程组成
线程
是最小的执行单位, 线程有自己的私有的栈空间和程序计数器
多线程
实现方式
Thread: 重写run方法
Runnable : 重写run方法,然后用Thread包装
Callable : 重写call方法, 然后包装成FutureTask,
然后再包装成Thread, -- call方法 有返回值
然后再包装成Thread, -- call方法 有返回值
不适合处理cpu密集型操作, 只适合处理io密集型任务
状态
创建: new Thread()
就绪: 调用start()
运行: 调用run()
阻塞: 暂时停止线程, 将线程挂起,
可能将资源交给其他线程 sleep, wait, join
可能将资源交给其他线程 sleep, wait, join
死亡: 线程销毁, 发生异常,
或者interrupt()方法打断
或者interrupt()方法打断
坏处
性能问题,上下文切换
锁
死锁
活锁
饥饿
好处
压榨硬件, 提升性能
JMM
特性
有序性
锁,volatile
指令重排, 内存屏障
as-if-serial
不管怎么重排序 单线程情况要保证结果不变
Happens-Before
先行发生模型:
不是取决于代码的顺序, 取决于执行结果的顺序
不是取决于代码的顺序, 取决于执行结果的顺序
可见性
锁,volatile
原子性
锁
jmm是抽象的, 操作空间是逻辑空间,
而jvm和硬件模型, 是真实存在的物理空间
而jvm和硬件模型, 是真实存在的物理空间
解决的问题
屏蔽掉不同操作系统的底层差异
cpu与内存的交互
缓存一致性协议
MESI,MSI
伪共享
volatile
保证可见性和有序性,
Volatile -- lock指令 -- 触发缓存一致性协议
Volatile -- lock指令 -- 触发缓存一致性协议
锁
Synchronized
序列化访问临界资源
应用场景
- 修饰实例方法,进入同步代码前要获得当前实例的锁
- 修饰静态方法,进入同步代码前要获得当前类对象的锁
- 修饰代码块,进入同步代码前要获得指定对象的锁
修饰方法: 隐式同步
修饰代码块: 显式同步
monitor
对象在内存中的布局
对象头
实例数据
填充数据
是由ObjectMonitor实现(c++编写)
EntryList
owner
waitSet
cas
通过cas, 比较并交换
1: 如果当前的owner为空, 则将owner更新成当前线程
2: 然后返回的值是上次owner的值, 如果为空, 就表示是重新给owner赋值了新的线程
3: 如果返回的是当前的线程, 那么就表示正在进行重入操作, 计数器++
1: 如果当前的owner为空, 则将owner更新成当前线程
2: 然后返回的值是上次owner的值, 如果为空, 就表示是重新给owner赋值了新的线程
3: 如果返回的是当前的线程, 那么就表示正在进行重入操作, 计数器++
两个结论, synchronized是通过cas得到锁的, 并且是可重入锁
锁优化(升级)
0: 无锁,
1: A线程进入同步代码块, cas修改对象头Mark Work
2: 进入偏向锁, 这时, B线程也进入了同步代码块
3: 发现已经被占用, 开始撤销偏向锁 (撤销就是在有两个线程, 其中有竞争关系发生的时候)
4: 升级成轻量级锁, 轻量级锁中再存在竞争的锁
5: 实现自适应自旋, 会根据你上一次成功的次数, 来设置
自旋操作替代了原本的阻塞场景, 因为阻塞, 唤醒, 都涉及到用户态和内核态的切换的, 也是有性能问题
长期自旋的话, 也不好, 长期占用时间片
6: 如果自旋次数到了, 还没获取到锁, 那么锁膨胀
7: 进入重量级锁
锁可以升级, 但是不能倒退, 在进入重量级锁后只有可能通过gc垃圾回收器处理成无锁
1: A线程进入同步代码块, cas修改对象头Mark Work
2: 进入偏向锁, 这时, B线程也进入了同步代码块
3: 发现已经被占用, 开始撤销偏向锁 (撤销就是在有两个线程, 其中有竞争关系发生的时候)
4: 升级成轻量级锁, 轻量级锁中再存在竞争的锁
5: 实现自适应自旋, 会根据你上一次成功的次数, 来设置
自旋操作替代了原本的阻塞场景, 因为阻塞, 唤醒, 都涉及到用户态和内核态的切换的, 也是有性能问题
长期自旋的话, 也不好, 长期占用时间片
6: 如果自旋次数到了, 还没获取到锁, 那么锁膨胀
7: 进入重量级锁
锁可以升级, 但是不能倒退, 在进入重量级锁后只有可能通过gc垃圾回收器处理成无锁
cas(乐观锁)
Compare and Swap 比较并交换
Compare and Swap 比较并交换
缺点
长时间自旋给cpu带来压力
ABA问题
只能针对一个属性具有原子性
应用
Atomic下的原子操作类基于cas实现
搭配Unsafe
compareAndSwapObject(Object o, long offset,Object expected, Object x);
compareAndSwapObject(Object o, long offset,Object expected, Object x);
AQS
抽象队列同步器
实现(Sync)
ReentrantLock
共享锁
CountDownLatch
Sync
一次性产品, 设置n个容量后, 当有n个线程进入,
既完全释放锁, 之后进来的线程不需要经过锁
既完全释放锁, 之后进来的线程不需要经过锁
Semaphore
Sync
类似火车站购票,可以同时存放n个线程,
任意一个执行完成, 即可释放锁, 接收新的线程
任意一个执行完成, 即可释放锁, 接收新的线程
屏障锁
CyclicBarrier
类似吃饭的时候, 需要等人到齐了才开始
0 条评论
下一页