JAVA高并发核心知识
2023-02-10 14:39:58 1 举报
AI智能生成
JAVA高并发知识点大杂烩
作者其他创作
大纲/内容
高并发知识点
重写RUN方法
new Thrad(Runnable a)
join() 用主线程阻塞办法确保多线程顺序
静态代码块,执行本地方法,注册系统资源registerNatives
调用本地方法,获取当前线程为父线程
获取系统安全管理,获取管理器的threadGroup,如果没有,从父线程获取
检查线程组权限
当前线程集成父线程相关属性
init
直接调用Runnable对象的run方法,因此不会执行任务。因为继承Runable必须实现run方法
run
添加当前启动的线程到线程组
调用本地方法启动线程
synchronized void start()
获取当前线程 Thread t = Thread.currentThread()
以当前线程为Key,获取ThreadLocalMap对象 ThreadLocalMap map = getMap(t)
set()
返回Thread 对象属性ThreadLocal.ThreadLocalMap threadLocals
获取的threadLocals变量不为空,返回本地变量对应的值
获取的threadLocals变量为空,返回setInitialValue()的值
get()
protected T initialValue(),空方法,由子类覆盖
调用初始化方法T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
判断map,设置值或创建map
返回value
setInitialValue()
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) m.remove(this);
注意:本地变量不适用一定要删除,否则会内存溢出
remove()
Object value
super(k), 即key为ThreadLocal弱引用对象
内部类Entry extends WeakReference<ThreadLocal<?>>
变量,Entry[] table
ThreadLoaclMap
内部类
关于ThreadLocal
变量 ThreadLocal.ThreadLocalMap threadLocals
new Thread时候执行init 方法,inheritableThreadLocals属性为空且父线程Thread属性inheritableThreadLocals不为空,则创建Thread 对象属性ThreadLocal.ThreadLocalMap inheritableThreadLocals。创建方法里调用了 重写方法childValue, 把父类的变量copy到子线程inheritableThreadLocals
重写父类ThreadLocal childValue()
返回Thread 对象属性ThreadLocal.ThreadLocalMap inheritableThreadLocals
重写父类ThreadLocal getMap()
重写父类ThreadLocal createMap()
InheritableThreadLocal
ThreadLoacl 不能值传递,其子类InheritableThreadLocal 可以
变量 ThreadLocal.ThreadLocalMap inheritableThreadLocals
Thread源码
stop 直接杀死线程,已不推荐使用
interrupt 通知线程中断。线程抛异常或主动检查
stop 和 interrupt 的区别
Thread (implements Runnable)
Subtopic
Runnable (interface)
直接new线程耗资源,缺少功能(定期,监控,中断),缺乏统一管理
TaskCallable
RunnableAdapter
DownloadTask
Callable (interface)
new FutureTask(Runnable a)
new FutureTask(Callable a)
FutureTask
子主题
RecursiveTask
RecursiveAction
ForkJoinTask implements Future
Future(interface)
原因:继承DateFormat,DateFormat有一个全局变量的Calendar变量,Calendar对象不支持多线程
局部变量:高并发时会创建大量对象,影响性能
synchronized :高并发时影响性能
lock:高并发时影响性能
ThreadLocal: 放在线程副本,适合高并发
解决:
SimpleDateFormat线程安全
线程数小于corePoolSize,直接创建线程,无论线程池的线程是否空闲
想降低系统资源,例如CPU使用率、corePoolSize设置小一点,队列容量大一点。降低线程池任务处理的吞吐量
相反利用系统资源,提升cpu使用率,可调大corePoolSize,缩小队列容量。但如果线程数量过大,会带来线程池调度问题,反而会降低任务执行的吞吐量
corePoolSize 核心线程数
线程数大于corePoolSize,小于maxmumPoolSize,当workeQueue满队时,才能创建线程
当corePoolSize==maxmumPoolSize,workQueue未满,则放入workQueue,等待空闲线程出现后取出执行
maxmumPoolSize 最大线程数
wokeQueue已满,没有可执行线程,执行拒绝策略
直接切换用常用队列是SynchronousQueue
严重时会OOM
要小于maximumPoolSize才会创建新线程,所以maximumPoolSize约等于不起作用
无边界阻塞队列 LinkedBlockingQueue
队列容量有限,可以降低资源损耗,但让线程调度变得困难
有边界阻塞队列 ArrayBlockingQueue
workQueue 阻塞队列,非常重要,对线程池有重大影响
keepAliveTime 空闲线程等待时间,超过则销毁,直接线程数了==corePoolSize
keepAliveTime的时间单位
unit
DefaultThreadFactory(静态内部类)
PrivilegedThreadFactory(静态内部类)
threadFactory
AbortPolicy 直接抛出异常,这也是默认的策略
CallerRunsPolicy 用调用者所在的线程来执行任务
DiscardOldestPolicy 丢弃队列中最靠前的任务并执行当前任务
DiscardPolicy 直接丢弃当前任务。
自定义策略 继承RejectedExecutionHandler
rejectHandler
构造方法参数
AtomicInteger类,保存线程数量和线程池的状态
ctl
ReentrantLock类,可重入锁
mainLock
重要属性
概念:锁和同步器的框架 (抽象队列同步器)
原理:共享占用资源。当前线程使用资源时锁定资源,其他线程被阻塞等待,等待机制时队列
原理图
ReentrantLock
Semaphore
SynchronousQueue
常用类
公平锁(先到先得),按照线程在队列排队方式,发现空闲资源,使用一次CAS抢锁,抢不到加入队列等待
优劣:非公平锁吞吐量大,但由于获取锁是靠抢,有可能让线程长时间出于饥饿状态
独占
概念:多个线程可以同时访问资源
概念:Semaphore相当于颁发线程获取许可证,许可证是共享得。Semaphore 经常用于限制获取某种资源的线程数量。
公平模式: 调用 acquire 的顺序就是获取许可证的顺序,遵循 FIFO;
非公平模式: 抢占式的。
Semaphore(信号量)
概念:允许count个线程阻塞再某处,直至所有线程执行完毕。
原理: 默认构造AQS的stat 为count,每个线程执行完毕后CAS操作减少stat
典型场景1: 启动服时,主线程等待其它组件加载
典型场景2: 并行,多个线程在同一时间起跑
CountDownLatch.countDown() count指减1
CountDownLatch.await()方法,阻塞主线程,直到count为1
使用不当容易死循环,例如count值为结束循环指标
CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次
问题
CountDownLatch(倒计时器)
概念:让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。简单说,拦截一堆线程,囤够了再一起工作
功能与CountDownLatch,但更复杂更强大
典型场景:多线程计算数据,最后合并结果
CyclicBarrier(循环栅栏)
共享
资源被定义两种方式
关于AQS
集成AQS(AbstractQueuedSynchronizer类)
extends AbstractQueuedSynchronizer implements Runnable
属性 final Thread thread;
调用外部ThreadPoolExecutor类的runWorker方法执行任务
Worker
四种拒绝策略也是内部类
重要内部类
通过ctl.get()获取线程数量,和corePoolSize等参数进行一系列逻辑处理
1在标记双循环里,首先检查特定条件是否允许加入线程
CAS操作成功,break retry; 结束双层循环
CAS操作失败,continue retry,从外循环开始,从新CAS操作
2通过1,进入内循环,通过cas方式(ctl.compareAndSet)增加线程
3通过2,取得全局独占锁ReentrantLock,把wokers 增加到wokers集合
或向阻塞队列增加等待任务
或执行拒绝策略
execute(Runnable)
execute()
submit()
shutdown()
shutdownNow()
getTaskCount()
getCompletedTaskCount()
getPoolSize()
getCorePoolSize()
getActiveCount() 当前线程池中正在执行任务的线程数量
监控类方法
1.获取woker和当前线程,获取woker任务,并且释放woker的锁
2.在while循环体中,判断任务不为空或者当前任务队列不为空,执行循环。否则执行processWorkerExit退出woker工作线程
3.循环中,获取woker独占锁,进行一系列逻辑判断是否需要中断当前线程的执行
5. 执行Runnable 的 run 方法
7. 设置完成任务为空,完成任务数+1,释放woker独占锁
8.执行processWorkerExit退出woker工作线程
runWorker(Worker w)
常用方法
ThreadPoolExecutor( 重要,线程池本质)
与ThreadPoolExecutor功能类似,结合ForkJoinTask类一起使用;ForkJoinTask.fork()执行任务,ForkJoinTask.join()返回结果汇总。ForkJoinTask需要实现方法exec(),进行任务拆解。常用RecursiveTask和RecursiveAction ,均属于ForkJoinTask的子类
ForkJoinPool(WorkStealingPool的本质,使用工作窃取算法)
AbstractExecutorService (abstract)
Timer是单线程模式,ScheduledThreadPoolExecutor是多线程模式
Timer调度是基于操作系统的绝对时间的,ScheduledThreadPoolExecutor调度是基于相对时间的
Timer不会捕获TimerTask抛出的异常,加上Timer又是单线程的。一旦某个调度任务出现异常,则整个线程就会终止,其他需要调度的任务也不再执行
Timer中执行的TimerTask任务整体上没有优先级的概念,不支持对任务的排序
Timer中执行的TimerTask类只是实现了java.lang.Runnable接口,不能返回结果
和Timer 的区别
ScheduledThreadPoolExecutor
ScheduleExecutorService
submit(Runnable a)
submit(Callable c)
method:
ExecutorService (interface)
Executors.newCachedThreadPool
Executors.newFixedThreadPool
Executors.newScheduleThreadPool
Executors.newSingleThreadPool
Executors.newSingleScheduleThreadPool
工作窃取算法(WorkStealing) 类似MapReduce思想
Executors.newWorkStealingPool
创建定长的线程池,超出最大并发数进入“无界队列”(Integer.MAX_VALUE)LinkedBlockingQueue<Runnable>等待,本质是new ThreadPoolExecutor,但阻塞队列没有边界,容易导致系统OOM
创建定长线程池,支持定时,周期性任务。超出最大并发数进入阻塞队列子类的DelayedWorkQueue(先入先出)。 本质是new ThreadPoolExecutor的子类ScheduledThreadPoolExecutor
和newFixedThreadPool一样,corePoolSize和maximumPoolSize为1。该线程池目的确保任务按顺序执行
和newScheduleTreadPool一样,corePoolSize设置为1
创建并行级别的work-stealing(工作窃取算法)线程池,本质是new ForkJoinPool()
工具类Executors
Running 运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务
Shutdown 关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于Running状态时,调用shutdown()方法会使线程池进入该状态
stop 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于Running或Shutdown状态,调用shutdownNow()方法,会使线程池进入该状态
tidying 如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量为0),线程池就会进入该状态。
terminated 处于Tidying状态的线程池调用terminated()方法,会使用线程池进入该状态
线程池实例状态
CPU密集型 CPU数量+1
IO密集型 CPU数量*2
线程数量配置建议
线程池 Executor (interface)
线程池状态是方法内部的处理,只需要了解即可
一个线程修改了共享变量,另一个线程立刻可见,否则有可见性问题
单核CPU因为只有一个CPU缓存,所以不存在可见性问题。
归根结底:可见性的问题是多核CPU的缓存导致的
可见性问题
多线程执行时候,CPU时间片段不断切换。一个或多个操作在CPU执行过程中出现中断
归根结底:原子性的问题是CPU对任务切换机制导致的
多核 32位CPU,多线程同时对Long类型写数据,会出现异常
原子性问题
有序性问题(指令重排)
程序次序规则
使用内存屏障
对一个使用了volatile变量的写操作,先行发生于后面对这个变量的读操作
volatile变量规则
程序次序规则+volatile变量规则
传递规则
进入synchronized代码块之前,会自动加锁,在代码块执行完毕后,会自动释放锁。
锁定规则
线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作
线程启动规则
线程A等待线程B完成,则线程A能够访问到线程B对共享变量的操作(Thread.join();)
线程终结规则
在线程A中中断线程B之前,将共享变量x的值修改为100,则当线程B检测到中断事件时,访问到的x变量的值为100。
线程中断规则
一个对象的初始化完成Happens-Before于它的finalize()方法的开始。
对象终结原则
Happens-Before
JVM禁用缓存和编译优化
解决可见性问题和指令重排
synchronized 使用了monitorenter 和monitorexit两条JVM指令锁定资源
synchronized 是非公平锁, 可重入锁
当使用synchronized关键字修饰代码块时,锁定的是实际传入的对象
当使用synchronized关键字修饰非静态方法时,锁定的是当前【实例对象】this
当使用synchronized关键字修饰静态方法时,锁定的是当前类的【Class对象】
隐藏规则
常见问题
阻塞
不支持响应中断
不支持超时
synchronized有局限性
为什么有synchronized又要提供lock
无法破坏思死锁必要条件的不可剥夺
synchronized
并发问题
加锁和解锁操作由同一个线程来完成
如何实现加锁和解锁的归一化
相同key
支持互斥性
设置key时候添加缓存有效期
支持锁超时
使用计数器来解决可重入问题
可重入方案(synchronized,lock支持可重入)
支持可重入
用自旋锁实现阻塞(死循环访问是否可以拿到锁)
非阻塞体验不友好,例如拿不到锁直接返回错误给前端
支持阻塞和非阻塞特性
支持高可用
用异步续命解决
加超时锁,任务没有执行完成,锁失效了
支持锁失效
分布式锁的基本要求
Redis实现分布式锁
概念:1 同一个线程下,外层方法上锁之后,内层调用的方法也能正常获取锁。 2 可重入锁指的是同一个线程可无限次地进入同一把锁的不同代码
RLock lock = redisson.getLock(\"anyLock\");
可重入锁(Reentrant Lock)
RLock fairLock = redisson.getFairLock(\"anyLock\");
公平锁(Fair Lock)
RLock lock1 = redisson1.getLock(\"lock1\");RLock lock2 = redisson2.getLock(\"lock2\");RLock lock3 = redisson3.getLock(\"lock3\");
联锁(MultiLock)
概念 :红锁是过半节点锁成功了,就算成功。解决连锁单点失效问题
红锁(RedLock)
概念: 读读共享,读写互斥
RReadWriteLock rwlock = redisson.getLock(\"anyRWLock\");
可重入读写锁(ReadWriteLock)
概念 :Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。
RSemaphore semaphore = redisson.getSemaphore(\"semaphore\");
semaphore.acquireAsync();semaphore.acquire(23);semaphore.tryAcquire();
redisson.getPermitExpirableSemaphore(\"mySemaphore\");
可过期性信号量(PermitExpirableSemaphore)
信号量(Semaphore)
RCountDownLatch latch = redisson.getCountDownLatch(\"anyCountDownLatch\");latch.trySetCount(1);latch.await();// 在其他线程或其他JVM里RCountDownLatch latch = redisson.getCountDownLatch(\"anyCountDownLatch\");latch.countDown();
闭锁(CountDownLatch)
Redisson实现分布式锁
允许一定程度的突发请求
令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌
令牌桶
平滑流入,固定流出
一个固定容量的漏桶,按照常量固定速率流出水滴
漏桶
计数器
限流算法
com.google.common.util.concurrent.RateLimiter rateLimiter = RateLimiter.create(5); rateLimiter.tryAcquire
Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现
单机限流
redis+lua实现的lua脚本
sentinel框架
分布式限流
限流方案
Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。然而这也意味着,执行一个较慢的lua脚本是不建议的,由于脚本的开销非常低,构造一个快速执行的脚本并非难事。但是你要注意到,当你正在执行一个比较慢的脚本时,所以其他的客户端都无法执行命令。
redis+lua 用lua脚本封装redis原子操作
redisson底层是使用lua脚本
redisson
如电商业务预防超卖,上传秒杀商品数据到redis
数据预热
准备阶段
例如:限流
例如:负载均衡层直接访问lua脚本,例如商品没有库存,直接返回已售完,不经过应用层
做好系统保护
秒杀阶段
处理一致性问题,业务补偿处理
结算阶段
秒杀三阶段
秒杀业务
线程A持有账户A的锁并等待线程B释放账户B的锁,线程B持有账户B的锁并等待线程A释放账户A的锁,死锁发生了
事例代码
一时间内资源只能一个线程等待,其它线程只能等待
互斥条件
线程拥有的资源只能自己主动释放
不可剥夺条件
保存自己的资源,同时对别的资源请求。别的资源在占用,自己的资源也不释放
请求与保持条件
循环等待条件
死锁四个必要条件
无法破坏,使用锁的目的就是为了互斥
破坏互斥条件
破坏不可剥夺的条件的核心就是让当前线程自己主动释放占有的资源,可以使用java.util.concurrent包下的Lock
破坏不可剥夺条件
可以一次性申请所需要的所有资源
破坏请求与保持条件
可以通过对资源排序,按照一定的顺序来申请资源,然后按照顺序来锁定资源,可以有效的避免死锁
例如转账操作中,每个账户都会有一个唯一的id值,我们在锁定账户资源时,可以按照id值从小到大的顺序来申请账户资源,并按照id从小到大的顺序来锁定账户,此时,程序就不会再进行循环等待了。
破坏循环等待条件
预防死锁,破坏四个必要条件
死锁问题
框架: Guava Cache、Ehcache 3.x、 MapDB
读取最快,容量受限,影响GC。一般用弱/软引用做缓存对象,使用存热点数据
堆内存
框架: Ehcache 3.x、 MapDB
减少GC,容量相对较多,读取数据时需要序列化/反序列化
堆外内存
可持久化
磁盘缓存
缓存空数据
布隆过滤
解决方案
缓存命中低(没有缓存),查询直接打到数据库
缓存穿透
热点数据缓存永不失效
批量数据设置不同的缓存失效时间
同一数据的数据库库访问设置并发锁
缓存时间失效,请求大量打到数据库
缓存击穿
Redis 高可用集群,必要时异地多活
限流
缓存数据集中失效;缓存服务器故障;请求大量打到数据库
缓存雪崩
分布式缓存问题
框架: Memcached、Redis
Java进程间分布式缓存
分布式缓存
存储空间达到多少清理,如10M
基于空间
储存容量达到多少条清理,如100条
基于容量
基于时间
只有在没有其他强引用对象引用弱引用/软引用对象时,垃圾回收时才回收该引用。即如果有一个对象(不是弱引用/软引用对象)引用了弱引用/软引用对象,那么垃圾回收时不会回收该弱引用/软引用对象。
基于对象引用
缓存回收策略
FIFO
LRU
LFU
回收算法
缓存
QPS
TPS
HPS
性能指标
响应时间
并发量
正确性
优化指标
有返回结果多线程,结合Future使用
0 条评论
回复 删除
下一页