导图社区 java学习重点之 java并发编程
java学习重点之java并发编程,从CPU架构到最新J.U.C并发包,列出了需要重点关注的知识点,比较深涩难懂,详细内容可根据关键字进行搜索,查看源码和案例。
编辑于2019-09-24 11:41:48JAVA并发编程
进程
内存模型
语言级的内存模型
Java内存模型/JMM
Java线程通信由Java内存模型(简称 JMM)控制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从抽象角度看,JMM定义了 线程 和 主内存 之间的抽象关系:线程之间的共享变量储存在主内存中,每个线程都有一个私有的本地内存,本地内存储存了 该线程 以读写共享变量的副本。
理论参考模型
顺序一致性内存模型/Sequential Consistency/SC
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性:一个线程中的所有操作必须按照程序的顺序来执行。(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
硬件级内存模型
TSO/整体存储定序/Total Store Ordering
数据载入间的执行顺序不可改变。数据存储间的顺序不可改变。数据存储同相关的它之前的数据载入间的顺序不可改变。数据载入同其相关的它之前的数据存储的顺序可以改变。向同一个地址存储数据具有全局性的执行顺序。原子操作按顺序执行。这方面的例子包括x86 TSO26和SPARC TSO.
PSO/部分存储定序/Partial Store Order
数据载入间的执行顺序不可改变。数据存储间的执行顺序可以改变。数据载入同数据存储间相对顺序可以改变。向同一个地址存储数据具有全局性的执行顺序。原子操作同数据存储间的顺序可以改变。这方面的例子包括SPARC PSO.
RMO/宽松内存定序/Relaxed Memory Order
CPU架构
精简指令集
RISC(reduced instruction set computer,精简指令集计算机)是一种执行较少类型计算机指令的微处理器,起源于80年代的MIPS主机(即RISC机),RISC机中采用的微处理器统称RISC处理器。这样一来,它能够以更快的速度执行操作(每秒执行更多百万条指令,即MIPS)。因为计算机执行每个指令类型都需要额外的晶体管和电路元件,计算机指令集越大就会使微处理器更复杂,执行操作也会更慢。
PowerPC
ARM
ARM架构,过去称作进阶精简指令集机器(Advanced RISC Machine,更早称作:Acorn RISC Machine),是一个32位精简指令集(RISC)处理器架构,其广泛地使用在许多嵌入式系统设计。由于节能的特点,ARM处理器非常适用于行动通讯领域,符合其主要设计目标为低耗电的特性。
MIPS
MIPS是世界上很流行的一种RISC处理器。MIPS的意思是“无内部互锁流水级的微处理器”(Microprocessor without interlocked piped stages),其机制是尽量利用软件办法避免流水线中的数据相关问题。它最早是在80年代初期由斯坦福(Stanford)大学Hennessy教授领导的研究小组研制出来的。MIPS公司的R系列就是在此基础上开发的RISC工业产品的微处理器。这些系列产品为很多计算机公司采用构成各种工作站和计算机系统。
复杂指令集
X86/Atom
xx86或80x86是英代尔Intel首先开发制造的一种微处理器体系结构的泛称。x86架构是重要地可变指令长度的CISC(复杂指令集电脑,Complex Instruction Set Computer)。Intel Atom(中文:凌动,开发代号:Silverthorne)是Intel的一个超低电压处理器系列。处理器采用45纳米工艺制造,集成4700万个晶体管。L2缓存为512KB,支持SSE3指令集,和VT虚拟化技术(部份型号)。
内存屏障
线程
状态
NEW 初始状态
RUNNABLE 运行状态
BLOCKED 阻塞状态
WAITING 等待状态
TIME_WAITING 超时等待状态
TERMINATED 终止状态
单线程
多线程
特性
可见性
happen-before
从 JDK5 开始,Java使用新的 JSR-133 内存模型。 JSR-133 使用 happens-before 的概念来阐述操作之间的内存可见性。在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,则这两个操作必须要存在happen-before关系 。happen-before 规则如下:程序顺序规则:一个线程中的每个操作,happen-before与该线程中的任意后续操作监视器锁规则:对一个锁的解锁,happen-before与随后这个锁的加锁volatile变量规则:对于一个volatile域的写,happen-before与任意后续对这个volatile域的读传递性: A happen-before B,B happen-before C,则A happen-before C
原子性
atomic
有序性
线程间通信
共享内存
volatile
保证可见性
不一定保证原子性
对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
保证有序性
禁止指令重排
编译器优化的重排
指令级并行的重排
内存屏障指令
内存系统的重排
锁
自旋锁
为了不放弃CPU执行时间,循环的使用CAS技术对数据进行尝试更新,直至成功
CAS
Compare And Swap比较并交换,线程修改主内存共享变量值前,先和原先拷贝的未操作前的变量值比较,若是相等(其他线程没动过)则替换,否则重新拷贝值再次计算。 CAS是一条CPU并发原语,调用sun.misc.Unsafe类 CAS比synchronized效率要好, 因为CAS是c语言实现的cpu锁机制, synchronized是Java锁。 缺点:可能产时间失败,循环时间长,CPU开销大只能保证一个共享变量的原子操作引出ABA问题
ABA问题
时间戳原子引用
增加修改版本号(时间戳) public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
悲观锁
假定会发生并发冲突,同步所有共享数据的相关操作,从读数据就开始上锁。
乐观锁
假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读取最新数据,然后重试修改。
独享锁(写)
给资源加上写锁,线程可以修改资源,其它线程不能再加锁;(单写)
共享锁(读)
给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁;(多读)
可重入锁
线程拿到一把锁之后,可以自由进入同一把锁同步的其他代码,则为可重入锁;否则是不可重入锁。
不可重入锁
公平锁
争抢锁的顺序,如果是按先来后到,则为公平锁;否则是非公平锁。
非公平锁
同步关键字
synchronized
用法
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
修饰方法
public synchronized void method(){ // todo}
修饰代码块
public void method(){ synchronized(this) { // todo }}
修饰静态方法
public synchronized static void method() { // 静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。}
修饰类
class ClassName { public void method() { synchronized(ClassName.class) { // todo } }}
ReentrantLock
ReentrantReadWriteLock
消息传递
线程间同步
同步是指 程序中用于控制不同线程间操作发生相对顺序 的机制
创建线程
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程
线程池
Executor框架
两级调度模型
Executor结构
线程池类型
FixedThreadPool 定长线程池
它是一种固定大小的线程池;corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
CachedThreadPool 可缓存线程池
它是一个可以无限扩大的线程池;它比较适合处理执行时间比较小的任务;corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
SingleThreadExecutor 单一线程池
它只会创建一条工作线程处理任务;采用的阻塞队列为LinkedBlockingQueue;
ScheduledThreadPool 可调度的线程池
它接收SchduledFutureTask类型的任务,有两种提交任务的方式:scheduledAtFixedRatescheduledWithFixedDelaySchduledFutureTask接收的参数:time:任务开始的时间sequenceNumber:任务的序号period:任务执行的时间间隔它采用DelayQueue存储等待的任务DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;DelayQueue也是一个无界队列;工作线程的执行过程:工作线程会从DelayQueue取已经到期的任务去执行;执行结束后重新设置任务的到期时间,再次放回DelayQueue
参数
int corePoolSize 核心线程数最大值
int maximumPoolSize 线程总数最大值
线程总数 = 核心线程数 + 非核心线程数
long keepAliveTime 非核心线程闲置超时时长
BlockingQueue workQueue
SynchronousQueue
LinkedBlockingQueue
ArrayBlockingQueue
DelayQueue
ThreadFactory threadFactory
RejectedExecutionHandler handler
AbortPolicy
CallerRunsPolicy
DiscardOldestPolicy
DiscardPolicy
关闭线程池
shutdown()
将线程池状态置为SHUTDOWN,并不会立即停止:停止接收外部submit的任务内部正在跑的任务和队列里等待的任务,会执行完等到第二步完成后,才真正停止
优雅的关闭
shutdownNow()
将线程池状态置为STOP。企图立即停止,事实上不一定:跟shutdown()一样,先停止接收外部提交的任务忽略队列里等待的任务尝试将正在跑的任务interrupt中断返回未执行的任务列表
立马关闭
awaitTermination()
当前线程阻塞,直到等所有已提交的任务(包括正在跑的和队列中等待的)执行完或者等超时时间到或者线程被中断,抛出InterruptedException
优雅的关闭,并允许关闭声明后新任务能提交
wait()
等待池/Wait Set
notify()
notifyAll
从wait set拿出所有线程使用notifyAll(通知全体)方法时,会将所有在wait set里苦等的线程都拿出来。其中有一个线程幸运儿能获得实例执行。
可靠
obj.notifyAll();
则会唤醒所有留在实例obj的waitset里面的线程。此方法和前面两个方法一样,在调用时,线程要获得此实例的锁,才能调用notifyAll方法。
结束线程
任务取消
通过volatile类型的域来保存取消状态
通过 future 的cancel取消线程
通过interrupt()方法中断当前线程
不推荐thread.stop()
停止基于线程的服务
关闭ExecutorService
毒丸对象
另一种关闭生产者-消费者服务的方式就是使用“毒丸”对象,其实就是指往对象里面放一个标志对象,当得到这个对象就立即停止,这就需要在执行方法里面判断,消费者读到毒丸后就不会再执行,同样生产者提交毒丸后,就不能再提交任务。
只执行一次的服务
JVM关闭钩子
关闭钩子本质上是一个线程(也称为Hook线程),用来监听JVM的关闭。通过使用Runtime的addShutdownHook(Thread hook)可以向JVM注册一个关闭钩子。Hook线程在JVM 正常关闭才会执行,在强制关闭时不会执行。 对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不能保证它的执行顺行。当所有的Hook线程执行完毕后,如果此时runFinalizersOnExit为true,那么JVM将先运行终结器,然后停止。Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间。另外由于多个钩子是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。使用场景: 1、内存管理 2、执行命令3、通过Hook实现临时文件清理
多线程设计模式
Future模式
当service(Main方法模拟)请求一个数据的时候,可以先给他返回一个包装类(空壳,代理对象,未来data,FutureData)然后开一个线程去异步加载真实数据,这样当service收到FutrueData,就可以做其他业务逻辑,当要用的时候,再从FutureData中的方法去加载真实数据。(类似ajax的思想)
统计功能
网络数据获取
Master-Woker模式
Master负责接收和分配任务
Worker负责处理子任务
当各个Worker子进程处理完成后,会将结果返回给Master,由Master做归纳和总结
Fork/Join框架
生产者-消费者
生产者线程负责提交用户请求
消费者线程则负责具体处理生产者提交的任务
在生产者和消费者之间通过共享内存缓存区进行通信。
Single Threaded Execution模式
Immutable模式
Guarded Suspension模式
Balking模式
Read-Write Lock模式
Thread-Per-Message模式
Work Thread模式
Two-phase Termination模式
Thread-Specific Storage(ThreadLocal)模式
J.U.C并发包
juc-locks 锁框架
Lock接口
ReentrantLock 可重入的独占锁
synchronized增强版
Condition接口
ReadWriteLock接口
ReentrantReadWriteLock 读写锁
LockSupport工具类
park()
阻塞当前调用线程
unark()
唤醒指定线程
AbstractQueuedSynchronizer抽象类(AQS)
模板方法设计模式
一套通用的机制来管理同步状态、阻塞/唤醒线程、管理等待队列
juc-atomic 原子类框架
AtomicInteger
以原子的方式更新int值
AtomicReference
以原子方式更新对象引用
Atomic数组
以原子的方式操作数组中的元素
AtomicXXXFieldUpdater
以一种线程安全的方式操作非线程安全对象的某些字段
LongAdder
比AtomicLong 具有更好的性能,代价是消耗更多的内存空间
juc-sync 同步器框架
CountDownLatch 倒数计数器
作为一个开关/入口
作为一个完成信号
CyclicBarrier 栅栏
让线程到达栅栏时被阻塞(调用await方法),直到到达栅栏的线程数满足指定数量要求时,栅栏才会打开放行
像军训报数,报数总人数满足教官认为的总数时,教官才会安排后面的训练
Semaphore 信号量
许可证
Exchanger 交换器
交换数据
Phaser 同步
juc-collections 集合框架
currentHashMap
ConcurrentSkipListMap
ConcurrentSkipListSet
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentLinkedQueue
ConcurrentLinkedDeque
BlockingQueue接口
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
DelayQueue
LinkedBlockingDeque
TransferQueue接口
LinkedTransferQueue
juc-executors 执行器框架
解耦任务本身和任务的执行