导图社区 java并发编程
java并发编程,完整总结,架构师第一步
编辑于2020-06-26 19:17:30一.java并发编程
1.线程基础、共享和协作
概念
线程和进程
进程分配的最小单位
cpu调度的最小单位
cpu核心和线程数
Intel引入超线程技术:1:2关系
普通:1:1
cpu时间片轮转
并发和并行
高并发意义
cpu利用率高
响应快
多线程注意事项
并发安全
死锁
线程数不能太多
线程
天生多线程
启动和终止
Thread是唯一抽象
Runable是对任务的抽象
new-ready-run-close+ block
过期:stop、suspend、resume
中断:interrupt ,Thread.interrupted()
深入理解线程
run和start
其他方法
wait\notify\notifyAll
join
优先级
1:10,默认5
守护线程
Thread.setDaemon(true)
协作和共享
共享
Synchronized内置锁
对象锁和类锁
不能对Integer,会每次new
volatile-最轻量的同步
ThreadLocal
实现原理
变量副本:隔离数据共享
set\get\remove!!!\initialValue
内部类:ThreadLocalMap
Entry[] table-数组
内部类-Entry<ThreadLocal,Object>
强软弱虚:弱引用-WeakReference
为了避免内存泄漏!!
内存泄漏
弱引用-THreadLocal-被回收
出现key为null的Entry
当前线程-强引用一直存在
get()、set()在某些时候,调用了expungeStaleEntry-清理!
根源:ThreadLocalMap的生命周期跟Thread一样长
ThreadLocal生命周期和所属对象一样!
协作
等待-通知机制
wait(long)
wait\notify\notifyAll
2.并发工具类
Fork-Join
分治思想
归并排序
原理:任务分解-结果合并
工作密取
ForkJoinPool、join()
CountDownLatch
使一个或多个线程等待其他线程完成各自的工作后再执行
发令枪
场景:并发测试
await/countLatch
CyclicBarrier
可循环使用(Cyclic)的屏障(Barrier)
一组线程相互等待
可重复使用
Semaphore
(信号量)是用来控制同时访问特定资源的线程数量
场景:数据库连接池、线程池
Exchange
(交换者)是一个用于线程间协作的工具类
线程间的数据交换
Callable\FutureTask
call-返回类型T
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结
FutureTask类实现了RunnableFuture接口, RunnableFuture继承了Runnable接口和Future接口, 同时实现了Callable接口 所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
3.原子操作CAS
利用CPU的多处理能力,实现硬件层面的阻塞
三个运算符:一个内存地址V,一个期望的值A和一个新值B
三大问题
ABA
变量前面追加上版本号
A→B→A就会变成1A→2B→3A
循环时间长开销大
只能保证一个变量
原子操作类
AtomicInteger
addAndGet-data
compareAndSet
getAndIncrement
getAndSet-data
AtomicIntegerArray
addAndGet(int i,int delta)
compareAndSet(int i,int expect,int update)
更新引用类型
AtomicReference
AtomicStampedReference 利用版本戳的形式记录了每次改变以后的版本号
AtomicMarkableReference 带有标记位的引用类型
原子更新字段类
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
4.显示锁和AQS
显示锁
标准用法:
p
tryLoc/ lockInterruptibly
ReentrantLock
可重入
公平锁和非公平锁
读写锁: ReentrantReadWriteLock
Condition接口
await/signal,signalALl
lock.newCondition()
LockSupport
一组以park开头的方法用来阻塞当前线程
unpark(Thread thread)方法来唤醒一个被阻塞的线程
CLH队列锁
一种基于链表的可扩展、高性能、公平的自旋锁
申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态
创建一个的QNode,将其中的locked设置为true表示需要获取锁
线程A对tail域调用getAndSet方法,使自己成为队列的尾部
线程就在前驱结点的locked字段上旋转
AbstractQueuedSyschronizer
AQS是CLH队列锁的一种变体实现
是用来构建锁或者其他同步组件的基础框架
使用了一个Volatile int成员变量表示同步状态, 通过内置的FIFO队列来完成资源获取线程的排队工作
模板模式-通过继承实现其他组件
三种模板方法 独占式获取与释放同步状态、 共享式获取与释放、 同步状态和查询同步队列中的等待线程情况
可重写方法:
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively
内部类Node 和Head-tail;
节点加入同步队列-双向链表!! 首节点的变化 独占式和共享shi-同步状态的获取和释放
Condition的实现-等待队列-单向链表!!
Lock实现
读写锁:锁的升降级
5.并发容器
hash和位运算
有符号右移>>(若正数,高位补0,负数,高位补1)
无符号右移>>>(不论正负,高位均补0)
取模a % (2^n) 等价于 a & (2^n - 1)
ConcurrentHashMap
ConcurrentSkipList系列
Map-有序Map
Set-有序Set
替代品:TreeMap和TreeSet使用红黑树按照key的顺序来使得键值对有序存储
二分查找和AVL树查找
先大步查找确定范围,再逐渐缩小迫近
跳跃表又被称为概率,或者说是随机化的数据结构
ConcurrentLinkedQueue 无界非阻塞队列,它是一个基于链表的无界线程安全队列
写时复制容器
CopyOnWriteArrayList
CopyOnWriteArraySet
CopyOnWriteMap
每次修改都创建一个新数组,然后复制所有内容
只能保证数据的最终一致性
阻塞队列BlockingQueue
ArrayBlockingQueue
使用了Condition来实现
LinkedBlockingQueue
PriorityBlockingQueue :无界
DelayQueue-无界-优先级
PriorityQueue
Delayed接口
SynchronousQueue:
每一个put操作必须等待一个take操
一个不存储元素的阻塞队列
LinkedTransferQueue-无界
多了tryTransfer和transfer方法
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列
等待通知模式实现
6.线程池
why-use
降低资源消耗
提升响应速度
提高线程的可管理性
ThreadPoolExecutor类关系
Executor是一个接口
ExecutorService接口继承了Executor, 在其上做了一些shutdown()、submit()的扩展, 可以说是真正的线程池接口
AbstractExecutorService抽象类实现了ExecutorService接口
ThreadPoolExecutor是线程池的核心实现类
ScheduledExecutorService接口继承了ExecutorService接口
ScheduledThreadPoolExecutor是一个实现类
参数含义
int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler
拒绝策略: 1.直接异常 2.覆盖最前面 3.覆盖最后面 4.调用者执行 5.自定义
扩展功能
调用 beforeExecute和 afterExecute方法。相当于执行了一个切面
工作机制
1.core;2.queue;3.max;4.reject
提交任务
execute()方法用于提交不需要返回值的任务
submit()方法用于提交需要返回值的任务
关闭线程池
shutdown或shutdownNow方法来关闭线程池
合理地配置
任务的性质:CPU密集型任务、IO密集型任务和混合型任务。 •任务的优先级:高、中和低。 •任务的执行时间:长、中和短。 •任务的依赖性:是否依赖其他系统资源,如数据库连接
预定义
FixedThreadPool
使用有界队列LinkedBlockingQueue
适用于负载比较重的服务器
SingleThreadExecutor
保证顺序地执行各个任务
使用有界队列LinkedBlockingQueue
CachedThreadPool
适用于执行很多的短期异步任务的小程序, 或者是负载较轻的服务器
ScheduledThreadPoolExecutor
SingleThreadScheduledExecutor
需要单个后台线程执行周期任务, 同时需要保证顺序地执行各个任务的应用场景
ScheduledThreadPoolExecutor
需要多个后台线程执行周期任务
CompletionService
看做是Executor和BlockingQueue的结合体
与ExecutorService最主要的区别在于submit的task 不一定是按照加入时的顺序完成的
7.并发安全
线程安全性
线程封闭
ad-hoc线程封闭 这是完全靠实现者控制的线程封闭
栈封闭--局部变量 栈封闭是我们编程当中遇到的最多的线程封闭。
无状态类
加final关键字
不提供任何可供修改成员变量的地方
不可变类
加锁和CAS
Synchronized、lock
安全的发布
类中持有的成员变量,如果是基本类型,发布出去,并没有关系
ThreadLocal
是实现线程封闭的最好方法
Servlet辨析
不是线程安全的类
死锁
多操作者(M>=2个)情况下,争夺多个资源(N>=2个,且N<=M); 且争夺资源的顺序不对
通过jps 查询应用的 id, 再通过jstack id 查看应用的锁的持有情况
其他安全
活锁;cpu在调度,但一个线程总是拿不到锁 随机sleep
线程饥饿 低优先级的线程,总是拿不到执行时间
并发性能
线程开销: 1.上下文切换 2.内存同步 3.阻塞
减少锁的竞争
减少锁粒度
减小锁范围
避免多余锁
分段锁
替换独占锁
使用读写锁, 用自旋CAS 使用系统的并发容器
安全的单例模式
volatile+双重检查锁定
懒汉式:私有静态内部类
枚举
8.实战项目
1.架构改进之路 服务化,文档生成并行化,采用生产者消费者模式
文档处理改进 1、解决方案:缓存避免重复工作、题目处理并行和异步化 2、如何实现 1)先检索缓存,新题目在生成时要考虑并发安全和充分利用Future 2)题目有更新时,怎么处理?
9.JMM和底层原理
内存分层
寄存器 L1-L2缓存-L3共享 主存DRAM 磁盘 云存储
工作内存和主内存
read-write load-store
作用于主内存的变量
主内存--执行引擎--工作内存
use-assign lock-unlock
作用于工作内存的变量
伪共享
以64字节为单位的块(chunk)拿取,称为一个缓存行(cache line)。
一个缓存行可以存储多个变量(存满当前缓存行的字节数); 而CPU对缓存的修改又是以缓存行为最小单位的, 在多线程情况下,如果需要修改“共享同一个缓存行的变量” ,就会无意中影响彼此的性能,这就是伪共享(False Sharing)
使用数据填充的方式来避免,即单个数据填充满一个CacheLin
JMM带来的问题
可见性
更新时:竞争
重排序
编译器、指令级、内存系统
数据依赖性
写后写 写后读 读后写
as-if-serial
控制依赖性
内存屏障
StoreLoad Barriers是一个“全能型”的屏障
临界区
临界区内的代码则可以重排序
happens-before
本质上和as-if-serial语义是一回事
happens-before关系 保证正确同步的多线程程序的执行结果不被改变
Volatile详解
当写一个volatile变量时, JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
当读一个volatile变量时, JMM会把该线程对应的本地内存置为无效
volatile关键字修饰的变量会存在一个“lock:”的前缀
重排序规则
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序
final的内存语义
1、在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序子主题
2、初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
final引用不能从构造函数内逃逸
写:构造函数return之前插入一个StoreStore障屏。 读final域的重排序规则要求编译器在读final域的操作前面插入一个LoadLoad屏障
锁的内存语义
核心-volatile
system.out.print
当进入synchronized语句块时, 子线程会被强制从主内存中读取共享变量
synchronized的实现原理
通过成对的MonitorEnter和MonitorExit指令来实
对象头
Mark-word:8字节
hasecode:25bit 分代年龄:4bit 是否偏向锁:1bit 锁标志位:2bit
类型指针:8字节
指针压缩:4字节
数组长度
各种锁
自旋锁-无锁-CAS
偏向锁
轻量级锁-共享锁
重量级锁-独占锁
锁粗化和消除
10.Java8新特性
原子操作
LongAdder
DoubleAdder
DoubleAccumulator
LongAccumulator
StampLock
读写锁的优化
StampedLock则提供了一种乐观的读策略, 这种乐观策略的锁非常类似于无锁的操作, 使得乐观锁完全不会阻塞写线程
CompleteableFuture
实现了Future<T>, CompletionStage<T>两个接口
CompletionStage是一个接口,从命名上看得知是一个完成的阶段
Lambda
三个部分,参数列表,箭头,主体
函数shi接口
只定义了一个抽象方法的接口(Interface), 接口中是否有默认方法,不影响
方法引用
其他补充
Disruptor
英国外汇交易公司LMAX开发的一个高性能队列
传统队列
队列的底层数据结构一般分成三种:数组、链表和堆
高性能
引入环形的数组结构:数组元素不会被回收,避免频繁的GC, 无锁的设计:采用CAS无锁方式,保证线程的安全性 属性填充:通过添加额外的无用信息,避免伪共享问题 环形数组结构是整个Disruptor的核心所在
浮动主题