Java并发编程
2020-05-25 14:06:45 1 举报
AI智能生成
Java并发编程的艺术——读书笔记。里面不仅有读书笔记,还有引用在读书过程中整理的一些不错的博文。Java并发编程是一个Java工程师必备的开发技能,在后续的过程中,我仍会不断更新此文档。也欢迎大家进行提问~
作者其他创作
大纲/内容
第6章 Java并发容器和框架
6.1 CurrentHashMap的实现原理和使用
面试官-对比一下HashMap、HashTable、CurrentHashMap
数据结构
Segment数组
HashEntry数组
原理
ConcurrentHashMap使用了2次散列运算,从而减少Hash冲突
定位HashEntry和定位Segment的散列算法虽然一样,都与数组的长度减去1再相“与”,但是相“与”的值不一样,
定位Segment使用的是元素的hashcode通过再散列后得到的值的高位,
而定位HashEntry直接使用的是再散列后的值
size()方法——ConcurrentHashMap是如何统计元素的个数的?
ConcurrentHashMap的做法是先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小
那么ConcurrentHashMap是如何判断在统计的时候容器是否发生了变化呢?
使用modCount变量,在put、remove和clean方法里操作元素前都会将变量modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化。
6.2 CurrentLinkedQueue
实现原理
第一是定位出尾节点;
第二是使用CAS算法将入队节点设置成尾节点的next节点,如不成功则重试。
第二是使用CAS算法将入队节点设置成尾节点的next节点,如不成功则重试。
6.3 Java中的阻塞队列
Java中的阻塞队列
❑ ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
❑ LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
❑ PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
❑ DelayQueue:一个使用优先级队列实现的无界阻塞队列。
应用场景
❑ 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了
❑ 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。
❑ SynchronousQueue:一个不存储元素的阻塞队列。
❑ LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
❑ LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
实现原理
通知模式
阻塞条件
队列已满,生产者对象无法插入
队列已空,消费者无对象可消费
阻塞原理
1、通过同步器AbstractQueuedSynchronizer中的内部类ConditionObject,定义了阻塞条件;
2、调用同步器的await方法完成阻塞
底层调用的是LockSupport.lock()
6.4 Fork/Join框架
概念
用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
工作窃取算法
双端队列
实现原理
ForkJoinPool
ForkJoinTask数组负责任务的提交
ForkJoinWorkerThread数组负责任务的执行
使用
ForkJoinTask
❑ RecursiveAction:用于没有返回结果的任务。
❑ RecursiveTask:用于有返回结果的任务
第7章 Java中的13个原子操作类
4种原子更新方式
原子更新基本类型
❑ AtomicBoolean:原子更新布尔类型。
❑ AtomicInteger:原子更新整型。
❑ AtomicLong:原子更新长整型。
❑ AtomicInteger:原子更新整型。
❑ AtomicLong:原子更新长整型。
原子更新数组
❑ AtomicIntegerArray:原子更新整型数组里的元素。
❑ AtomicLongArray:原子更新长整型数组里的元素。
❑ AtomicReferenceArray:原子更新引用类型数组里的元素。
❑ AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下。
❑ AtomicLongArray:原子更新长整型数组里的元素。
❑ AtomicReferenceArray:原子更新引用类型数组里的元素。
❑ AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下。
原子更新引用
❑ AtomicReference:原子更新引用类型。
❑ AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
❑ AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。
❑ AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
❑ AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。
原子更新属性
❑ AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
❑ AtomicLongFieldUpdater:原子更新长整型字段的更新器。
❑ AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
❑ AtomicLongFieldUpdater:原子更新长整型字段的更新器。
❑ AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
第二步,更新类的字段(属性)必须使用public volatile修饰符。
第8章 Java中的并发工具类
等待多线程完成的CountDownLatch
同步屏障CyclicBarrier
cyclicBarrier.isBroken()判断当前线程是不是被中断
控制并发线程数的Semaphore
实现原理
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
应用场景
流量控制
线程间交换数据的Exchanger
实现原理
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。
应用场景
遗传算法
校对工作
CyclicBarrier和CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置
CyclicBarrier更适合复杂一些的场景
总结
CountDownLatch、CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段
Exchanger工具类则提供了在线程间交换数据的一种手段
第9章 Java中的线程池
作用
1、降低资源消耗
2、提高响应速度
3、提高线程的可管理性
线程池的创建和使用
runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列
❑ ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
❑ LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
❑ SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
❑ PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
RejectedExecutionHandler(饱和策略)
❑ AbortPolicy:直接抛出异常。
❑ CallerRunsPolicy:只用调用者所在线程来运行任务。
❑ DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
❑ DiscardPolicy:不处理,丢弃掉。
向线程池提交任务
execute()方法用于提交不需要返回值的任务
submit()方法用于提交需要返回值的任务
关闭线程池
shutdown
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
shutdownNow
shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
配置线程池
❑ 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
❑ 任务的优先级:高、中和低。
❑ 任务的执行时间:长、中和短。
❑ 任务的依赖性:是否依赖其他系统资源,如数据库连接。
第10章 Excutor框架
Executor框架的两级调度模型
Executor框架的成员
ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors
第11章 并发编程实践
第2章 原子操作的实现原理
处理器:提供总线锁定和缓存锁定两种机制
基础内存操作:自动保证基本的内存操作原子性
复杂内存操作:跨总线宽度、跨多个缓存hang和跨页表的访问
总线锁:LOCK#信号,其他处理器请求阻塞
缺点:开销大,不推荐
缓存锁:缓存一致性——处理器1缓存内存地址时,处理器2是不允许缓存的
Java实现原子操作的两种实现方式
锁
除了偏向锁,其他所都用了CAS
循环CAS
ABA问题:变量追加版本号
循环时间开销大:JVM支持CPU提供的pause指令进行优化
只能保证一个共享变量的原子操作:合并变量方式解决
第3章 Java内存模型
Java内存模型
happens-before(JSR-133)
规则
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
监视器锁规则:对于一个锁的解锁,happens-before于随后对这个锁的加锁
volatile变了规则:对一个volatile域的写,happens-before于任意后续对这个域的读
传递性:如果A hb B,B hb C,则A hb C
功能:阻止编译器、处理器的重排序,保证JMM的内存可见性
并发编程模型的分类
线程之间的通信机制有两种
共享内存:显式同步,隐式通信
消息传递:隐式同步
重排序
数据依赖性:编译器和处理器不会改变依赖关系的两个操作的执行,单处理器指令序列和单个线程中的操作
as-if-serial语义:允许对存在控制依赖的操作做重排序
单线程中不改变执行结果
多线程可能影响执行结果
顺序一致性内存模型
参考理论模型:每个操作都必须原子执行且立刻对所有线程可见
两大特性
一个线程中的所有操作必须按照程序的顺序来执行
不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序
总结:JMM内存模型和顺序一致性内存模型的差异是什么?
顺序一致性模型示意图
JMM内存模型
volatile的内存语义
一个volatile变量的单个读写操作,与一个普通变量在读写操作时加同步锁,二者的执行效果相同
变量特性
可见性
原子性
语义实现
重排序规则
先读,后写
内存屏障
首先确保正确性,然后再去追求执行效率
写屏障:StoreStore|volatile写|StoreLoad
读屏障:volatile读|LoadLoad|LoadStore
比较
volatile
volatile仅仅保证对单个变量的读写具有原子性,在可伸缩性和执行性能上,volatile更有优势。
锁
锁的互斥执行的特性,可以确保对整个临界区代码的执行具有原子性。在功能上,锁比volatile更强大;
锁的内存语义
锁的实现
旧的实现方式是锁住总线内存;新的方式是使用缓存锁定来保证指令执行的原子性。
一,利用volatile变量的写-读所具有的内存语义。
二,利用cas所附带的volatile读和volatile写的内存语义。
公平锁
非公平锁
总结
concurrent包实现模式
1)申明共享变量为volatile
2)使用cas的原子条件更新来实现线程之间的同步。
3)配合volatile的读写和cas所具有的volatile读和写内存语义来实现线程之间的通信。
final域的内存雨语义
写final域的重排序规则。
禁止编译器把域的写重排序到构造函数之外。
读final域的从排序规则。
先读取对象引用,再读取final域的操作对象。
final域为引用类型
第4章 Java并发编程基础
4-1 线程简介
为什么要使用多线程
更多的处理核心
更快的响应时间
Java提供了良好的并发模型
线程也有优先级
范围1~10,默认是5
线程状态
NEW
RUNNING
BLOCKED
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态
WAITING
进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态
TIME WAITING
超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态
TERMINATED
Daemon线程
在Java虚拟机退出时Daemon线程中的finally块并不一定会执行;故,在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑
4-2 启动和终止线程
构造线程
启动线程
理解中断
线程通过检查自身是否被中断来进行响应。
中断方法
不同线程状态对中断操作的反应
对于处于new和terminated状态的线程,中断操作对这两种状态下的线程是无效的;
RUNNBALE、Blocked状态下的线程遇到中断操作,也只会设置中断标志位并不会实际中断线程运行;
WATING/TIME WATING状态的线程遇到中断,会抛出一个InterruptedException异常,并清空中断标志位
过期的suspend()、resume()和stop()
安全的终止线程
方法1:线程使用volatile变量标记;
方法二:使用中断标识,判断中断状态为true,跳出循环
4-3 线程间通信
volatile和synchronized关键字
volatile可以保证内存的可见性
synchronized则要线程竞争锁
同步块和同步方法实现方式上有什么区别?
通知/等待机制
经典范式
等待方
1)获取对象的锁。
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
通知方
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
管道输入/输出流
Thread.join()
在当前线程中,让另外一个线程(执行join的线程)执行完毕后,再进行当前线程的逻辑
ThreadLocal
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构
4-4 线程应用实例
等待超时模式
简单数据库连接池示例
线程池技术及实例
线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断地从工作队列上取出工作并执行。当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务之后会通知任意一个工作者线程,随着大量的任务被提交,更多的工作者线程会被唤醒。
一个基于线程池技术的简单web服务器
Tomcat、jetty等都有线程池技术的应用
时序图
第5章 Java中的锁
5.1-Lock接口
Lock接口提供的synchronized关键字不具备的主要特性 P120
尝试非阻塞的获取锁
能被中断地获取锁
超时获取锁
5.2 队列同步器(AbstractQueuedSynchronizer),俗称的AQS
用来构建锁或其他同步组件的基础框架
int变量标识同步状态
通过内置的FIFO队列完成资源获取线程的排队工作
设计模式-模板方法
访问或修改同步状态
getState()-获取当前线程的同步状态
setState()-设置同步状态
compareAndSetState(int expect,int update)-使用CAS设置当前的状态,该方法能够保证状态设置的原子性
同步器可重写的方法
同步器提供的模板方法
1、独占式获取与释放同步状态
2、独占式获取与释放同步状态
3、查询同步队列中的等待线程情况
实现原理
独占式同步状态获取流程
P128
共享式同步状态获取与释放
独占式超时获取同步状态的流程
P133
doAquireNanos(int args,long nanosTimeout)
在指定的时间段内获取同步状态,如果获取到返回true,否则false
自定义同步组件——TwinsLock
Node
等待状态
SIGNAL
-1
CANCELLED
1
CONDITION
-2
PROPAGATE
-3
0
不同的子类实现
ReentrantLock
ReentrantReadWriteLock
Semaphore
CountDownLatch
ThreadPoolExecotor
几篇不错的博文
AQS子类的tryAcquire和tryRelease的实现
5.3 重入锁
概念
支持一个线程对资源的重复加锁
重入的实现原理是怎么样的?
加锁的时,已经有锁,state值不断累加
释放锁时,state不算减少,直到为0
公平锁和非公平锁的比较
非公平锁
非公平锁性能更好,但是有可能造成线程饥饿
公平锁
公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换
5.4 读写锁
概念
读写锁维护了一对锁,一个读锁和一个写锁。在同一时刻可以允许多个读线程访问;但是在写线程访问时,所有的读线程和其他写线程均被阻塞
应用场景
读多写少
可以支持更高的并发
实现
ReentrantReadWriteLock
特性
公平性选择
读写锁都支持重入
锁降级
遵循获取写锁、获取读锁,再释放写锁;
写锁可以降级为读锁
读写状态的设计
依赖自定义同步器实现同步功能
按位切割使用
高16位表示读;低16位表示写。高读低写
三个实现
写锁的获取与释放-WrightLock.tryWrightLock()
重入条件
无读锁
写锁是本线程
支持重入的排它锁
读锁的获取与释放
支持重入的共享锁
重入条件
无写锁
读锁是本线程
锁降级
概念:写锁降级为读锁
应用场景:保证数据的可见性
博客推荐
ReentrantLock中非公平锁与公平锁的性能测试
5.5 LockSupport工具
功能:提供线程阻塞和唤醒的方法
blocker
功能:blocker是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题排查和系统监控
5.6 Condition接口
功能:Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式
Condition的方法及功能
应用场景
有界队列
概念:有界队列是一种特殊的队列。
当队列为空时,队列的获取操作将会阻塞获取线程,直到队列中有新增元素;
当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”
当队列为空时,队列的获取操作将会阻塞获取线程,直到队列中有新增元素;
当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”
Condition的实现分析
jdk1.8中有2个实现
AbstractQueuedSynchronizer的内部类ConditionObject
AbstractQueuedLongSynchronizer的内部类ConditionObject
等待队列
等待
通知
阅读心得
第2-3章 比较偏向操作系统底层,以及偏理论上的一些东西
第4-6章 是重点核心,作者带我们查看了关于并发的底层实现原理
第7-10章 比较偏向应用实战,在实际场景中去使用才能有更好的收获
0 条评论
下一页