java并发
2021-03-25 18:37:47 9 举报
AI智能生成
整理不对地方,请指正
作者其他创作
大纲/内容
线程的并发工具类
CountDownLatch
栅栏、计数器
用于使一系列线程在同一时刻一起执行
先定义出 CountDownLatch 的 容量,在需要等待的地方执行 await() 在需要执行触发的地方执行 countDown() 方法,直到 CountDownLatch 的容量为0 则执行 await() 之后的业务方法
CyclicBarrier
循环屏障
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
还多了一个回调函数功能
Semaphore
信号量,进行线程限流
springcloud 断路器 实现 Hystrix
一种线程池
一种信号量
Exchanger
线程之间交换数据
两个线程之间在达到某个同步点的时刻进行数据交换,一般不会用
Callable、Future和FutureTask
FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
CompletableFuture
Fork/Join思路
什么是分而治之
将问题划分为多个子问题,从子问题中寻找最优解,是个自底向上的过程
工作窃取
即当前线程的Task已经全被执行完毕,则自动取到其他线程的Task池中取出Task继续执行。
通常使用双端队列,执行线程从头部执行任务,窃取线程从尾部拿取任务执行
并发容器
ConcurrentHashMap
1.7中HashMap死循环分析
HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,
一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。
一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。
HashTable是如何实现线程安全的
HashTable容器使用synchronized来保证单操作线程安全
哈希冲突可解决办法
开放地址法,如果冲突,再找一个没引用的地址存入
再散列函数法
链地址法
实现
sizeCtl
-1代表正在初始化
-N 表示有N-1个线程正在进行扩容操作
正数或0代表hash表还没有被初始化
jdk7
数据结构
数组(初始化之后可扩容,默认大小16),负载因子
链表
锁思想
Segment
分段锁
对每个元素的hashCode再进行一次哈希散列算法,这样做的好处是让元素分布均匀在不同的Segement中
get操作不需要加锁,在于将共享变量设置为volatile
扩容只会对对应的Segment进行扩容操作,而不是对整个Map
jdk8
数据结构
数组+单向链表+红黑树,阈值分别为6和8
改进
取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对缩小锁的粒度,进一步减少并发冲突的概率,并大量使用了采用了 CAS + synchronized 来保证并发安全性。
其他并发容器
ConcurrentLinkedQueue(无界非阻塞队列)
基于链接节点的无界线程安全队列,它采用先进先出的规
则对节点进行排序,LinkedList的并发版本。
则对节点进行排序,LinkedList的并发版本。
使用CAS来进行入队出队
写时复制容器,如CopyOnWriteList
写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
弊端
读和写分别是不同的对象,读不到最新的内容
只能保证最终一致性
适用场景
都多写少的场景
阻塞队列
概念
支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
场景
常用于生产者/消费者模式
常用队列
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
读锁和写锁公用一个锁,直接操作枚举对象,必须初始化队列大小,使用可重入锁来实现公平锁特性
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
读锁和写锁是分开的,将枚举对象包装为Node<E>才可以进行操作,可以不指定队列大小,默认是 Integer.MAX_VALUE
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
DelayQueue:一个使用优先级队列实现的无界阻塞队列
场景使用
缓存到期
限时支付
SynchronousQueue:一个不存储元素的阻塞队列
每一个put操作之后,必须跟着get操作,吞吐量较高
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
CAS
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
Disruptor
传统队列问题
利用加锁来保证线程安全问题
存在伪共享问题
更新
使用环型数组结构
查找效率不低,不会被垃圾回收,省去了GC的操作
使用CAS操作
避免了创建/销毁锁的开销
添加额外的填充数据,消除伪共享
数据满了之后,会从0开始覆盖
只有一个角标 sequence 来标识这个环的起点
线程池
为什么要用线程池
降低资源消耗
便于线程管理
提高响应速度
实现原理
判断核心线程是否都在执行任务,如果不是,则创建一个线程执行任务
判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这
个工作队列里
个工作队列里
判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程
来执行任务。如果已经满了,则交给饱和策略来处理这个任务
来执行任务。如果已经满了,则交给饱和策略来处理这个任务
线程池的创建
核心参数
corePoolSize(线程池基本大小)
当提交一个任务到线程池时,线程池会创建一个线
程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任
务数大于线程池基本大小时就不再创建
程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任
务数大于线程池基本大小时就不再创建
没有任务需要执行的时候,线程池的大小不一定是corePoolSize
maximumPoolSize 最大线程数
队列满了会启用 最大线程数来创建新的线程
ThreadFactory
自定义线程的名字格式
runnableTaskQueue(任务队列)
保存等待执行任务的队列
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
keepAliveTime
线程保持活动时间
TimeUnit
时间单位,配合keepAliveTime使用
RejectedExecutionHandler(饱和策略)
AbortPolicy
抛出异常(默认策略)
CallerRunsPolicy
使用调用者线程来执行任务
DiscardOldestPolicy
丢弃队列中最靠前的任务,并执行当前任务
DiscardPolicy
直接丢弃任务,采用不理睬的对策
线程池提交
execute()
没有返回值,无法知晓线程执行成功
submit()
有返回值提交
线程池关闭
shutdown
告诉线程池一个通知,未执行工作的线程会被立即关掉
shutdownNow
线程池的状态设置成
STOP,然后尝试停止所有的正在执行或暂停任务的线程
STOP,然后尝试停止所有的正在执行或暂停任务的线程
Executor框架
ThreadPoolExecutor框架
FixedThreadPool
固定线程大小
SingleThreadExecutor
单线程,保证顺序执行
CachedThreadPool
适用于执行很多的短期异步任务的小程序,或者
是负载较轻的服务器
是负载较轻的服务器
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
SingleThreadScheduledExecutor
适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
WorkStealingPool
利用所有运行的处理器数目来创建一个工作窃取的线程池,使用forkjoin实现
FutureTask
线程基础,线程之间的共享和协作
基础概念
cpu核心数,线程数
cpu时间片轮转机制
进程和线程
并行和并发
性能指标
吞吐量
单位时间内能处理的请求数量。吞吐量越高,说明性能越好
延迟
发出请求到收到响应的时间。延迟越小,说明性能越好
并发
能同时处理的请求数量,一般来说随着并发量的增加、延迟也会增加
线程所需要满足的特性
原子性
可见性
有序性
存在共享数据并且该数据会发生变化,通俗地讲就是有多个线程会同时读写同一数据,会受到上述三个特性的干扰
死锁
一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象
必备的四个条件
互斥,共享资源 X 和 Y 只能被一个线程占用
占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X
破坏: 一个线程直接申请所有所需的共享资源
不可抢占,其他线程不能强行抢占线程 T1 占有的资源
破坏: 当一个线程等待另一个线程持有资源获取不到的时候,将这个线程所持有的资源释放掉
循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待
破坏:按序申请资源来预防
活锁
任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程
让线程随机休眠一段时间
饥饿锁
一个线程因为 CPU 时间全部被其他线程抢占而得不到 CPU 运行时间,导致线程无法执行
优先级线程吞噬所有的低优先级线程的 CPU 时间
其他线程总是能在它之前持续地对该同步块进行访问,线程被永久堵塞在一个等待进入同步块
其他线程总是抢先被持续地获得唤醒,线程一直在等待被唤醒
java线程基础
启动和终止线程
实现线程方式
继承Thread类,重写run方法
单继承的局限性
基于Runnable接口实现
执行完毕之后,并不会有返回值返回
基于Callable接口实现
可以利用 FutureTask<V> 进行包装,然后利用 future.get() 拿到返回值
如何安全的中止线程
建议使用interrupt操作
不用stop原因
终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,
因此会导致程序可能工作在不确定状态下
因此会导致程序可能工作在不确定状态下
线程再认识
线程状态
初始状态
实现线程,new操作就会变为初始状态
运行状态
线程的start方法
阻塞状态
有时限等待
无时限等待
终止状态
线程状态之间转换
runnable到blocked的状态
synchronized锁
runnable到waiting的状态
Object.wait方法
Thread.join()
LockSupport.park()(与wait和notify功能类似)
runnable到timed_waiting状态
Thread.sleep()方法
waiting状态那些方法带时间参数
runnable到termanited状态
run方法执行完
stop方法()
不建议使用
该方法会立即中断线程,如果持有ReentrantLock锁,并不会去释放该锁,太危险
中断方法 --interrupt
会通知该线程,其可以做一些后续操作
线程间的共享
synchronized
用法
给对象加锁
当前实例加锁
类锁
对某些静态方法进行加锁
volatile
实现原理
使用load,write屏障来实现
使用场景
线程之间需要加标志位实现变化操作
禁止指令重排序
读多写少的场景
ThreadLocal
用法
当前线程创建的唯一副本,各个线程之间变量独自享有。(以空间换时间的设计思路)
shiro框架中使用
分布式事务框架 seata 中 ,进行保存 Xid 的时候
数据结构
ThreadLocalMap
内存泄露分析
因为使用的时弱引用类型,所以只要ThreadLocal设置为null,就会被垃圾回收。但是ThreadLocalMap和ThreadLocal是朝生夕灭的,就会存在key值删除,而value存在的情况,导致内存泄漏
解决方法:使用完之后,调用remove方法
线程间协作
等待通知机制
wait/notify/notifyall
必须配合synchronized关键字使用
join方法
A.join(b),A线程执行完了才会执行B线程
调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?
yield() sleep() 不会去释放锁
wait() 会释放锁
notify() 对锁没有影响,但是线程只有在syn同步代码执行完后才会自然而然的释放锁,所以notify()系列方法一般都是syn同步代码的最后一行。
原子操作CAS
java中的原子类都是基于CAS实现
CAS原理
循环 and 交换
问题
ABA问题
循环,加大cpu开销
线程中自旋锁也会出现这问题,但是java将其升级为了自旋适应锁
只能保证一个变量的共享操作
原子操作类使用
基本类型
数组
对象引用
乐观锁类似,加个版本戳
Java8新增
LongAdder
LongAccumulator
显示锁和AQS
显示锁
Lock接口
与synchronized的区别
Lock可以手动释放锁,synchronized关键字不能手动释放,异常释放或者正常释放
synchronized是非公平锁,lock可以通过构造器设置公平和非公平锁
都是可重入锁
ReentrantLock类(独享锁)
可重入锁
一个线程可以多次获取该锁
公平锁/非公平锁
ReadLock接口
ReentrantReadWriteLock(读写锁),适用于读多写少的场景
读锁(共享锁)
当没有线程持有写锁时,所有线程都可以使用读锁,但是只要存在写锁,那所有读线程必须的进行堵塞
读锁不能升级为写锁
写锁(排他锁)
写锁可以降级为读锁
如果存在读锁,那么写锁是不能被获得
jdk8中的StampLock
读写锁的升级版
不支持可重入锁
写锁
悲观读锁
乐观读
它允许在有读锁情况下,允许一个线程获取写锁,在读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写!即读写之间不会阻塞对方,但是写和写之间还是阻塞的!
建议是如果乐观读期间,发现值被修改了,那么就应该将其设置为悲观读锁
Condition接口
用Lock和Condition实现等待通知
AQS(AbstractQueuedSynchronizer)
学习必要
基本上JUC包底下大部分的锁都是基于AQS设计实现的
核心思想
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制
构建锁或者其他同步组件的基础框架
使用了一个volatile int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作
使用了一个volatile int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作
CLH队列
一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系
一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋
CAS方法设置尾节点
设计模式
模板方法
0 条评论
下一页