《java并发编程的艺术》阅读笔记总结
第1章 并发编程的挑战
并发编程的目的是为了让程序运行得更快,但是不是更多的线程就能让程序最大限度的并发执行。比如上下文切换、死锁的问题,以及受限于软件和硬件的资源限制问题。
软件资源限制:有数据库的链接数和socket连接数等
硬件的资源限制有带宽的上传、下载速度、硬盘读写速度和CPU处理速度。
减少上下文切换的方法
- 无锁并发编程
- CAS算法
- 使用最少线程
- 使用协程
避免死锁
- 避免一个线程获得多个锁
- 避免一个线程在锁内同时占有多个资源
- 尝试使用定时锁带替代内部锁机制
- 数据库锁,加锁和解锁在一个数据库连接里
建议使用并发容器和工具类来解决并发问题
第2章 并发机制的底层实现原理
java代码在编译后会变成java字节码,字节码被类加载器加载到jvm当中,jvm执行字节码,最终需要转化为汇编指令在CPU上执行。
在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized。
volatile
特性:
- 可见性
如果对声明了volatile的变量进行写操作,jvm就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会系统内存,同时根据处理器的缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的数据是否过期,过期就要设置无效重新加载。
- 禁止指令重排
synchronized
实现同步的基础,java中每一个对象都可以作为锁:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是Synchonized括号里面的配置对象
每个对象有一个monitor与之关联,获得monitor获得锁,代码块同步是使用monitorenter和monitorexit实现的,插入到同步代码块的前后位置。检查是否持有monitor所有权,即尝试获得对象的锁
偏向锁
为了解决统一线程多次活动一个锁,需要切换上下文等代价。是一种等到竞争出现才释放锁的机制
做法:
- 在锁对象的对象头和栈帧的锁记录里存储锁偏向的线程ID
- 如果检测没有偏向锁,检测Mark Word中的锁标识是否设置为1,如果没有设置就是用CAS竞争锁
- 如果设置了1,就尝试用CAS将对象的偏向锁指向当前线程
轻量级锁
加锁:
- 在当前线程的栈帧中创建用于存储锁记录的空间,将对象头中的Mark Word复制到锁记录中,然后尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针,失败则与其他线程竞争,尝试自旋获得锁。
解锁:
- CAS将栈帧中存储的Mark Word替换回对象中,失败就代表存在竞争,膨胀为重量级锁
锁升级过程
这几种都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。
原子操作的实现:
处理器
使用基于缓存的加锁或基于总线的加锁
- 基于总线。使用处理器提供的一个LOCK 信号,当一个处理器在总线上输出此信号时,其他处理器的请求被阻塞,该处理器独占总线。
- 基于缓存。使用缓存一致性原则
java
使用锁和循环CAS的方式实现
- 循环CAS,循环进行CAS直到成功。存在三大问题:
1. ABA问题
2. 循环时间长开销大
3. 只能保证一个共享变量的原子操作,可以考虑把多个变量合成,或者使用锁
- 使用锁。偏向锁,轻量级锁和互斥锁。除了偏向锁,其他实现锁的方式都使用了循环CAS获得和释放锁
第3章 java内存模型
并发编程需要解决两大问题,线程如何通信和同步
通信机制有两种,共享内存和消息传递。共享内存是通过读写内存中的公共状态进行隐式通信,消息传递是发送消息进行显示通信
同步是指程序中用于控制不同线程间发生相对顺序的机制。
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,存储了副本
重排序?
第4章 java并发编程基础
设置线程优先级时,频繁阻塞(休眠或IO)的线程需要设置较高优先级,偏重计算(需要较多CPU时间或偏运算)的线程则设置较低的优先级,确保处理器不会独占。
Daemon线程用作支持性工作,在java虚拟机退出时Daemon线程的finally块并不一定会执行,所以不能依靠finally确保关闭或清理资源。
interrupt()方法中断线程,对于阻塞状态线程会抛出InterruptedException并且清除中断标识。
wait()/notify()
一个线程A调用了对象O的wait方法进入同步队列等待状态,另一个线程B调用了对象O的notify或者notifyAll方法,线程A收到通知后等待B释放锁之后,从wait状态返回。
Thread.join(),当前调用join的线程A等待线程Thread线程终止之后回来。
ThreadLocal,线程变量,一个以ThreadLocal对象为键、任意对象为值的存储结构。
线程池。一方面消除了频繁创建和消亡线程的系统资源开销,另一方面,面对多任务的提交能够平缓的劣化。
线程池的本质就是使用了一个线程安全的队列链接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程可以不断从工作队列取出线程。
第5章 java中的锁
Lock接口
使用synchronized可以隐式的获得锁,支持重进入,先获得后释放,固化了所的过程,Lock接口可控性更强
队列同步器(AbstractQueueSynchronizer)
是用来构建锁或者其他同步组件的基础框架,提供了getState(),setState()和compareAndSetState来进行操作。
同步队列,依赖内部的FIFO双向队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器将当前线程和等待状态等信息加入同步队列,同时阻塞线程。
还有些内容,需要仔细再阅读
重入锁ReentrantLock
包含synchronized的基本功能,还加入了公平性、等待可中断、等待条件等功能。
公平锁可以减少饥饿发生的概率,等待越久的请求越容易优先满足,但是会频繁切换线程,降低吞吐量,所以非公平锁是默认。在获得锁的条件增加了在同步队列中该节点是否有前驱节点的判断。
如何实现:
判断当前线程是否为获得锁的线程来决定操作是否成功,如是则增加同步状态值返回true,释放锁的时候要减少状态值。
读写锁 ReentrantReadWriteLock
一对锁,同一时刻可以允许多个读线程访问,但是写线程访问时,所有的读线程和写线程阻塞。
状态设计
用一个整型变量代表状态,高16位表示读,低16位表示写。
写锁的获取与释放
是一个支持重进入的排它锁。如果当前读锁已经被获取或者写锁在其他线程手里,阻塞。
如果存在读锁,写锁不能获取。原因在于,写锁要保证操作对读锁可见,如果存在读锁的时候对写锁获取,那么正在运行的其他线程就无法得知当前写线程的操作。
读锁的获取与释放
支持重进入的共享锁。没有其他写线程就可以访问。
锁降级
保持住写锁,再获得读锁,随后先释放写锁。一个线程有写锁是可以加读锁的
适用于读优先于写,如数据库的读写,读线程一来写线程就要降级
Condition接口
需要首先获得锁,主要使用await和signal方法。调用await(),当前线程会释放锁,其他线程调用Condition对象的signal()方法,当前线程才从await()返回,并且已获得了锁。
第6章 java并发容器和框架
ConcurrentHashMap
在并发编程中使用HashMap可能会导致死循环(resize()方法),而线程安全的HashTable效率非常低(synchroniezd标记方法如put),所以使用ConcurrentHashMap。
锁分段技术
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构构成。Segment是一种可重入锁。HashEntry是用于存储键值对数据。
一个ConcurrentHashMap有一个Segment数组,每个Segment数组中又有一个HashEntry数组,每个HashEntry是一个链表。
初始化
get()
先进行一次再散列,然后使用这个散列值通过散列运算定位到Segment。get操作不需要加锁,所以高效,这需要将get里面使用的共享变量都定义为volatile类型。对volatile字段的写操作先于读操作。
定位Segment是hashcode再散列之后值的高位,而定位HashEntry是直接使用再散列之后的值。
(hash>>> sgementShift ) & segmentMask
hash & (tab.length - 1)
put()
定位Segment之后
1. 判断是否需要对HashEntry数组扩容
Segment是在插入元素之前判断,HashMap是在插入之后判断。创建一个两倍的数组,在散列插入新数组,只对某个segment扩容
2. 定位添加元素的位置,放进去
ConcurrentLinkedQueue
实现线程安全队列的两种方式,阻塞方式(用锁),非阻塞方式(CAS)
入队列
入队节点添加到队列的尾部。
- 第一步将入队节点设置为当前队列尾节点的下一个节点。
- 更新tail。如果tail下一个不为null,更新为入队节点,如果为空,不移动(CAS)
tail不总是尾节点,需要通过tail节点来找到尾节点。 HOPS,tail和尾节点的长度大于常量才更新。
这种设计是通过增加对volatile节点的读操作,减少了写操作,写操作开销大,所以入队效率高。
出队列
从队列头返回一个节点。
当队列头有元素,直接返回不更新head,当head为空,出队更新head,通过hops来减少cas更新head节点的消耗。
阻塞队列
支持阻塞插入和移除的方法。
java里的阻塞队列:
- ArrayBlockingQueue,一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueue,一个由链表组成的有界阻塞队列
- PriortyBlockingQueue,一个支持优先级排序的无界阻塞队列
- DealyQueue,使用优先级队列实现的无界阻塞队列
- SynchronousQueue,不存储元素的阻塞队列
- LinkedTransferQueue,由链表组成的无界阻塞队列
- LinkedBlockingDeque,由链表组成的双向阻塞队列
Fork/Join框架
把大任务切割成小任务,最后汇总结果
工作窃取算法
队列从别的队列窃取任务执行。把子任务分别放到不同的队列里,并为每个队列创建单独的线程。窃取从尾部拿任务
ForkJoinTask
创建ForkJoin任务,重写compute,是否足够细分,小任务join
ForkJoinPool,执行任务
类似线程池
第7章 原子工具类
第8章 java并发工具类
CountDownLatch
允许一个或多个线程等待其他线程完成操作。类似join功能,完成一定顺序的执行。
调用countDown,N-1,await阻塞当前线程直到n为0。await等待n为0
CyclicBarrier
可循环使用的屏障,让一组线程到达一个屏障被阻塞,直到最后一个到达,屏幕开门继续执行。
await()阻塞等待,还可以提供构造函数,都到达了优先执行barrierAction。
可用于多线程计算数据合并结果。
区别:
CountDownLatch只能使用一次,CyclicBarrier可以使用reset()复用
Semaphore
信号量,控制同时访问特资源的线程数量,用作流量控制,不满就可以进去(acquire\release),满了等着
Exchanger
线程间写作的工具类,数据交换。
线程池
好处:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性。
ExecutorService exec = Executors.newSingleThreadExecutor();ExecutorService exec = new ThreadPoolEcecutor(各种参数);
实现原理:
核心池,线程池,工作队列,等待时间
- 线程池判断核心池是否满了,如果没有创建新线程执行,如果满了下一阶段
- 判断工作队列是否已满,没满加入,满了继续下一个流程
- 判断线程池线程是否已满,没有创建新线程执行,满了使用饱和策略(四种:抛异常、用调用者线程执行、对其队列最近一个任务执行这个、不处理丢弃)
线程池创建线程时,会将线程封装成工作线程Worker,执行任务,执行完毕后会从工作队列循环获取继续执行。
提交任务:
- excute,不需要返回值
- submit,需要返回值,返回一个futrue类型的对象,使用get方法获得返回值,在线程任务完成之前一直阻塞
关闭线程池
调用interrupt方法
- shutdown,设置状态SHUTDOWN状态,中断没有执行任务的线程
- shutdownNow,设置状态为stop,尝试停止正在执行或暂停任务的线程,返回等待执行的列表
合理配置线程池
考虑因素:
- 任务的性质:CPU密集型、IO密集型和混合型任务
- 优先级
- 执行时间
- 任务依赖性
CPU就尽量小,CPU+1,IO尽量多,2*CPU
建议使用有界队列
Executor框架
jdk5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,执行机智由Executor框架
两级调度模型
在VM中,java线程会被一对一映射为本地操作系统线程。在上层,java多线程通常把应用分解为若干个任务,然后使用Execturo框架将这些任务映射为固定数量的线程,在底层,操作系统内核将这些线程映射到硬件处理器上
结构
- 任务
- 执行 ThreadPoolExecutor,ScheduledThreadPoolExecutor
- 结果 Future接口实现类FutreTask
成员
- ThreadPoolExecutor
- FixedThreadPool,限制数量
- SingleThreadExecutor,单个线程,保证按顺序执行
- CachedThreadPool,大小无界的线程池,执行多起异步小任务
- ScheduledThreadPoolExecutor
适用于执行周期任务 - Future接口
表示结果
详解:
FixedThreadPool把corePoolSize和maximumPoolSize都设置为n,同时keepAliveTime为0,多余的空闲线程立即终止。使用无界队列,max和keep都没用了,不会拒绝任务
CachedThreadPool,core为0,max为Integer.MAX_VALUE,keepalivetime为60s,使用没有容量的SynchronousQueue作为工作对垒
《java并发编程的艺术》阅读笔记总结相关推荐
- Java并发编程的艺术-阅读笔记和思维导图
最近在坚持每天阅读<<Java并发编程的艺术>>,不但做好笔记(MarkDown格式),还做好思维导图. 如果大家感兴趣,可以可以到码云上阅读笔记和到ProcessOn上阅读思 ...
- 《Java并发编程的艺术》笔记
<Java并发编程的艺术>笔记 第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种 ...
- JAVA并发编程的艺术-读书笔记
1.并发编程的挑战 多线程并不一定能带来性能提升,相反过多的线程导致线程创建和上下文切换有时会比单线程性能更低 无锁并发编程:根据数据id进行取模,不同的线程处理不同段的数据 死锁:资源互相等待,线程 ...
- [转] 《Java并发编程的艺术》笔记
转自https://gitee.com/Corvey/note 作者:Corvey 第一章 并发编程的挑战 略 第二章 Java并发机制的底层实现原理 volatile的两条实现原则: Lock前缀指 ...
- [书]java并发编程的艺术笔记
本文属于自己整理的读书笔记,便于回顾.内容绝大部分来自书籍:java并发编程的艺术,版权归原作者所有. 第1章 并发编程的挑战 1.多线程一定比单线程快? 不一定,如同在同时阅读两本书时的来回切换切换 ...
- 【Java并发编程的艺术】读书笔记——Java并发编程基础
学习参考资料:<Java并发编程的艺术> 文章目录 1.线程的几种状态 2.如何安全的终止线程 3.线程间通信(重要) 3.1共享内存 3.2消息传递 1.线程的几种状态 线程在运行的生命 ...
- 《Java并发编程的艺术》——Java并发的前置知识(笔记)
文章目录 一.并发编程的挑战 1.1 上下文切换 1.1.1 多线程一定快吗 1.1.2 如何减少上下文的切换 1.2 死锁 死锁发生的条件 预防死锁 避免死锁 1.3 资源限制的挑战 1.3.1 什 ...
- 《Java并发编程的艺术》——线程(笔记)
文章目录 四.Java并发编程基础 4.1 线程简介 4.1.1 什么是线程 4.1.2 为什么要使用多线程 4.1.3 线程优先级 4.1.4 线程的状态 4.1.5 Daemon线程 4.2 启动 ...
- 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)
文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...
- 《Java并发编程实践》学习笔记之一:基础知识
<Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念: (2)进程:是一种活动,它是由一个动作序列组成 ...
最新文章
- SharePoint 2013 工作流之使用Visio设计篇
- 关于C语言的问卷调查
- 将数据导入到mysql_06955.10.2如何将CM的外部PostgreSQL数据库迁移至MySQL服务
- 2015年中国钢铁企业排名50强名单
- python死锁案例_python避免死锁方法实例分析
- 使用Linux进行c或c++编程
- 深层神经网络——激活函数去线性化
- 算法应用-百钱买百鸡
- linux:记录一次 处理tomcat启动卡死无报错现象的曲折过程
- c语言从入门到精通的几个阶段
- 视觉SLAM十四讲学习笔记——ch9后端1
- Excel最强玩法!只用Excel就能做出「王者荣耀」战绩表
- 固态硬盘测试软件有哪些,SSD测试软件有哪些?SSD测试软件盘点
- 关于路由器和交换机的区别图解(总结)
- NVIDIA Jetson TK1学习与开发(四):一些细节问题
- Python 在Windows上终止子过程(subprocess)
- MATLAB算法实战应用案例精讲-【数据分析】时序异常检测(补充篇)(附Java、R语言和python代码实现)
- 支付10秒倒计时链接页面跳转
- ANSYS Workbench 16 - 黄志新(图书阅读总结)
- 10道前端面试题(带答案)
热门文章
- c语言char s[] 语句,35、若有定义和语句: char s[10]=abcd;printf(%s\n,s); 则结果是(以下u代表空格)...
- jmeter查看平均响应时间_线上服务平均响应时间太长,怎么排查?
- Windows10系统安装好用的截图软件--snipaste
- 【并查集】HAOI破译密文
- Security:osquery 介绍
- python大数据课程_Python课程(大数据系列)ElasticSearch从基础与实战视频课程
- VC++实现打开文件和打开所在文件夹的功能(附源码)
- 回文数——Java实现
- 蒙特卡洛方法 matlab 实现
- python像素鸟游戏