思维导图:高并发、多线程、锁、并发编程工具包、concurrent
附:文本结构
高并发
Nginx
限制请求数
limit_req_zone
zone=one:10m rate=1r/s
burst=5
限制并发连接数
limit_conn_zone
perip
perserver
几个术语
并行
(真)多个任务在多条线同时进行
如果CPU只有一个,那多进程和多线程都仅仅是并发的
并发
一条线交替进行多个任务,只是看起来像是同时进行多个任务(假)
并发等级
阻塞
排队
悲观
无饥饿
公平、不受优先级影响,先到先得
阻塞
无障碍
大家同时执行,冲突出错了就回滚
乐观
依赖一个“一致性标志”
1、在操作之前读取并保存这个标志;
2、操作中,如果有修改操作要再次读取这个标志,对比是否发生了变化;
变化了
3、不进行修改
没变化
3、改变这个标志
4、修改操作
最弱的非阻塞
无锁
前提是至少一个线程能在有限步内完成操作离开临界区
否则所有线程都会一直等待
运气不好,会出现类似饥饿问题,比如个别线程会一直等待
while (!atomicVar.compareAndSet(localVar, localVar+1)) {
localVar = atomicVar.get();
}
无限循环,直到本地值跟实际值相同
本地=实际时,修改实际值
do
开始修改、写入等操作
无等待
前提是所有线程都必须在有限步内完成
这样就不会出现饥饿问题
典型的无等待
Read Copy Update
只修改原始数据的副本
(读可以不加控制的原因)
修改后,在合适的时机回写数据
JMM
Java 内存 模型
原子性
可见性
有序性
同步
等着返回结果才继续
异步
任务在另一个线程中执行,不影响调用者继续干自己的事
多进程
一个程序至少有一个进程
一个进程至少有一个线程
多线程
系统分配执行线程的cpu,可能在同一个核心,也可能在多个核心
临界区
公共资源、公共数据
一旦临界资源被一个线程占用,那其他线程要使用它就就必须等待
打印机
写文件
锁
分类
乐观锁/悲观锁
乐观锁
实现
每次读数据都认为别人不会修改,所以不会上锁,只有在写的时候会判断下别人有没有修改过
使用版本号等机制
适用场景
读多的场景,提高吞吐量
实例
java.util.concurrent.atomic下的原子变量类
使用了CAS(比较并交换)实现
写入时,比较内存最新版本和预期版本,一致则写入新值(同时内存版本+1)
悲观锁
实现
每次读数据都认为别人会修改,所以总是上锁,这样别人读数据需要阻塞等待直到拿到锁
适用场景
写多的场景
实例
Synchronized关键字的实现就是悲观锁
独享锁/共享锁
独享锁
该锁一次只能被一个线程持有
实例
可重入锁
Synchronized
共享锁
该锁可被多个线程锁持有
并发读更高效,但读写、写读、写写的过程是互斥的
实例
读写锁的读锁是共享锁,写锁是独享锁
互斥锁/读写锁
独享、共享是广义的说法
互斥、读写是具体实现
互斥锁
ReentrantLock
可重入锁可通过构造方法指定是否为公平锁,默认是非公平锁
读写锁
ReadWriteLock
公平锁/非公平锁
公平锁
先申请的先得即公平
非公平锁
不保证先到先得,吞吐量比公平锁更大
可重入锁可通过构造方法指定是否为公平锁,默认是非公平锁
但可能会造成优先级反转或者饥饿现象
可重入锁
又名《递归锁》
比Synchronize更强大
增加了
轮询
超时
中断
...高级功能
是独享锁
是互斥锁
是悲观锁
可重入锁可通过构造方法指定是否为公平锁,默认是非公平锁
分段锁
分段锁是锁的一种实现形式
ConcurrentHashMap就是通过分段锁的形式来实现高效的并发操作
Put的时候,先通过hashcode来知道她要放在哪一个分段中,然后对这个分段加锁
所以在多线程put的时候,只要不是放在同一个分段中,就实现了真正的并行的插入
自旋锁
尝试获取锁的线程不会立即阻塞,而是会采用循环的方式去尝试获取锁
优点
减少线程上下文切换的消耗
缺点
循环消耗cpu
偏向锁/轻量级锁/重量级锁
偏向锁
如果一段同步代码一直被一个线程访问,那该线程会自动获取锁
降低获取锁的代价
轻量级锁
当锁是偏向锁时,被另一个线程访问,就会升级为轻量级锁,其他线程会通过自旋的形式去获取锁
重量级锁
当锁是轻量级锁时,自旋不会一直持续下去,当自旋一定次数时,还没获取到锁,就会进入阻塞,该锁膨胀为重量级锁
JUC并发编程工具包
java.util.concurrent
五大方面
锁
locks
ReentrantReadWriteLock
实现了读写锁(ReadWriteLock)
ReadLock
WriteLock
实现了可重入锁(ReentrantLock)
不提供加锁服务,只提供锁
获取锁的方法
ReentrantReadWriteLock.readLock()
ReentrantReadWriteLock.writeLock()
StampedLock
三个模式
独享写
悲观读
乐观读
LockSupport
是一个线程工具类
静态工具类
所有方法都是静态的
方法
park
用于阻塞当前线程
unpark
用于唤醒被阻塞的线程
Condition
Lock
原子变量
atomic
AtomicInteger
get()
获得当前值
getAndXxx()
获得操作前的值并进行Xxx操作
XxxAndGet()
Xxx操作并获得操作后的值
compareAndSet(expect, update)
和期望值相等则更新为update值,并返回true
不等则不更新
并且返回true/false
AtomicReference
让引用到处可见,并保持原子性
AtomicLong
AtomicBoolean
AtomicArray
DoubleAdder
分头计算,最后汇总,参考mapreduce思想
LongAccumulator
比LongAdder功能强大
可自定义计算规则
并发框架
Executor
三大件
工作单元
Runnable
不关注执行结果(不返回)
Callable
关注执行结果(有返回)
工作单元执行
Executor
一个接口,定义了execute方法
ExecutorService
Executor的子接口,定义了一系列线程池的基本操作接口
execute(Runnable)
submit(Callable or Runnable)
shutdown
关闭新的外部任务提交
shutdownNow
尝试中断正在执行的所有任务
isTerminated
是否所有任务已经关闭
isShutdown
是否该ExecutorService已被关闭
invokeAll(List<C or R>)
有很多实现子类
工作单元执行结果
Future
定义了对任务执行结果的取消、状态查询、结果获取方法
cancel
isCanceled
isDone
get
是接口
FutureTask
Future的唯一实现类
其他
Executors
用于快速创建各类线程池
ScheduledExecutorService
定长的线程池
支持定时或周期性执行
FixedExecutorService
定长的线程池
防止线程滥用
CachedExecutorService
可灵活伸缩线程数
没有空闲线程回收,则创建新线程,有则回收再利用
SingleExecutorService
单线程的线程池
可指定执行顺序
FIFO
LIFO
优先级
ForkJoin
三大件
执行任务的线程池
ForkJoinPool
方法
ForkJoinTask<T> = pool.submit(forkJoinTask)
创建方式
new ForkJoinPool()
使用可用的处理器数量
ForkJoinPool.commonPool()
最大化使用全局系统资源
线程池中具体执行任务的线程
ForkJoinWorkerThread
由ForkJoinPool内部自行创建维护,不需显式地使用此类
运行在线程池中的任务
ForkJoinTask
抽象类
定义了主要操作接口
fork
join
invoke
主要实现
RecursiveAction
不关心结果
RecursiveTask
关心结果
并发容器
collections
ConcurrentHashMap
对HashMap的升级
采用分段锁,提高了并发度
相比synchronize HashMap的全局加锁更加高效
读写都加锁
在多线程中使用可不用再自己实现任何并发控制
CopyOnWriteArrayList
适合读多写少的场景
拷贝写列表
写数据时,先拷贝一个副本,再写入副本
多份写同时进行,只保留一个结果,因此有一定风险
读不加锁
提高读效率
LinkedBlockingQueue
阻塞队列
方法
插入操作
add
立即返回
成功=true
无空间时则抛出:illegalStateException
offer
立即返回
成功=true
无空间时=false
可指定超时时间,超时=false
put
无空间时阻塞等待,直至插入成功
取出操作
poll
可指定超时时间
获取并移除队首元素
超时无元素返回则返回null
take
获取并移除队首元素,没有元素则等待
ConcurrentLinkQueue
ConcurrentSkipListMap
同步工具
tools
Semaphore
信号量(许可证管理工具)
用于限制同时访问资源的线程数
方法
acquire
申请许可
可定义申请数量
阻塞方法
申请不到时会一直阻塞,因此申请数量不能大于许可证总数
tryAcquire
申请许可,并立刻返回结果
不阻塞
可定义申请数量
可定义超时时间
release
释放许可
和Synchronize的区别
前者可控制一个或多个并发
许可数为1的时候,两者无区别
CountDownLatch
同步计数器
可用于监听多线程的执行情况
可用于分头处理任务,最后总结处理
方法
countDown
计数器减一
await
阻塞到计数为0,才被唤醒执行
也可设置超时时间,到时间了计数不为0也可以继续往下执行
CyclicBarrier
循环栅栏(循环屏障)
类似同步计数器
所有线程都释放了对这个屏障的使用后,可重复使用这个屏障
方法
CyclicBarrier (int parties)
仅指定并发数,不指定动作
CyclicBarrier (int parties, Runnable barrierAction)
指定次数达成后,执行某个指定动作
await
等待指定次数达成后才被唤醒
也可指定超时时间
Phaser
移相器
更倾向重用的循环栅栏
方法
register
注册一个同步者
arriveAndAwaitAdvance
到达并提前等待
arriveAndDeregister
到达并取消注册
getRegisteredParties
获得注册的同步者数量
forceTermination
强制终止phase,phase对象不再可用
Exchanger
交换者
一对线程到达同步点时,可进行数据交换
方法
Object v2 = exchange(Object v1)
交出V1,得到对方的v2
一手交钱一手交货
可定义超时时间
并发控制
几个操作
新建
继承Thread,重写run接口
实现Runnable的run接口
运行
start
停止
使用stopme标志
中断
interrupt
等待
对synchronize资源有效
线程进入等待区
唤醒通知
notify
随机唤醒一个等待的线程
不公平
notifyAll
唤醒全部等待的线程
推荐
挂起和继续执行
suspend
不会释放资源
resume
继续
早已废弃
不推荐
等待线程结束
targetThread.join()
当前线程会等待目标线程之行结束,再往下执行
targetThread.join(long mills)
指定最多等待的时间,如果等不及了就不等了,继续往下执行
谦让
yield
让出cpu资源一瞬间,重新跟其他线程竞争
可以在做完重要的事后谦让,也可以让不太重要的或者优先级低的线程,多谦让谦让
volatile
设置变量可见性
让多线程能够看见变量被修改后的值
线程组
给Thread类一个组名,提供可读性
例如Thread-0==》WorkerThread-0
Daemon守护线程
如果程序没有用户线程在运行,就会退出
JIT线程
GC线程
普通线程t.setDaemon(true)
将线程t设置为守护线程,而不是用户线程,那其他线程都结束后,此线程也将被结束。
优先级
setPriority(1~10)
优先级越大越优先
synchronized
本质只有两种级别
对象级别
对实例加锁
对方法加锁
对代码块加锁
类级别
对class加锁
对静态方法加锁
增强版锁
重入锁
ReentrantLock
Re- Entrant-Lock
显性、更加灵活
增加了轮询、超时、中断等高级功能
需要自行加锁,解锁
可配置多个锁
可用于解决死循环
lock()
获得锁,如果被占用则等待
unlock()
释放锁
tryLock()
不等待,没获得锁就立即返回false
tryLock(long,TimeUnit)
等待一定的时间,如果没等到就可以返回超时失败了
构造方法(true)
设置为公平锁
Condition条件
lock.newCondition
await()
类似wait
signal()
类似notify
signalAll()
类似notifyAll
应用
并发容器
ConcurrentHashMap
高效的线程安全的HashMap
ConcurrentSkipListMap
跳表、快速查找
BlockingQueue
一个接口
阻塞队列
ArrayBlockingQueue
满了就等待take
空了就等待put
ConcurrentLinkedQueue
线程安全的LinkedList
ConcurrentLinkedQueue
CopyOnWriteArrayList
多读少写的场景,性能好于Vector
读写分离锁
ReentrantReadWriteLock
readLock()
writeLock()
读读不阻塞
读写阻塞
写读阻塞
写写阻塞
倒数等待锁
new CountDownLatch(10)
countDown()
倒数一次
await()
等待倒数完成
循环栅栏(重复利用的倒数等待锁)
CyclicBarrier
线程阻塞工具类
LockSupport
park()
unpark()
比suspend和resume更安全
不可变对象
Integer
String
如果对不可变对象加锁,那将无效,因为不可变对象的值变化其实意味着此对象变成了新的对象,而线程看不到
因此加锁的对象要慎重
线程池
更高效
ThreadPoolExecutor
Executors.newFixedThreadPool(32)
用完回归再利用,不需要重新new Thread
fixedThreadPool可防止线程滥用
线程池有队列等待区,新提交的任务如果没有资源则会排队等待
LinkedBlockingQueue
SynchronousQueue
超出制定最大值会拒绝
不保存等待队列
不超出如果没有空闲则会来一个创建一个线程
自定义线程池
实现ThreadFactory
Nthreads = Ncpu * Ucpu * ( 1 + W/C )
Nthreads
最优线程池大小
Ncpu
cpu数量
Runtime.getRuntime().availableProcessors()
Ucpu
目标cpu的利用率
比如想要利用50%的cpu数目
W/C
等待时间/计算时间
Netty
异步的
高性能
高可靠性
网络应用程序框架和工具
开发快速
开发简单
用途
大容量传输文件
聊天
高并发
NIO非阻塞传输
阻塞业务处理
不阻塞数据接收
传输快
零拷贝
不经过缓冲区,而是直接进内存
封装好
代码封装好,功能完备
五种IO
阻塞IO(BIO)
适合连接少的场景
延迟最低
非阻塞IO(NIO)
适合高并发、处理简单的场景
接收同步,处理异步
多路复用IO
一个连接,接收和处理数据使用的可能是两个线程
信号驱动IO
主要用于嵌入式开发
异步IO
适合高并发、长连接的场景
接收和处理都是异步的