这本书集合JDK的源码讲了Java并发框架、线程池的原理等,深入到JVM、CPU层面来讲解。推荐看过《Java多线程编程核心技术》之后,可以继续研究此书,提高自己。全书分为11章,下面将记录个人认为每章中重要的知识点。
一、并发编程的挑战并发编程的目的是为了让程序执行的更快,并不是启动更多的线程就能让程序最大限度的并发执行。需要考虑很多因素,比如上下文切换、死锁,以及硬件和软件资源的限制。
1、上下文切换CPU通过时间片分配算法来循环执行任务,当前任务执行完一个时间片后会切换到下一个任务,切换前会保存上一个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换会影响多线程的执行速度。
(1)如何减少上下文切换?
减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程:
无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免使用锁。
CAS算法:Atomic包使用CAS算法来更新数据,不需要加锁。
使用最少线程:避免创建不需要的线程,比如任务很少,如果创建了很多线程,那么会造成大量线程处于等待状态。
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
注意:可以通过减少WAITING的线程,来减少上下文切换次数。因为每一次WAITING到RUNNABLE都会进行一次上下文的切换。
2、死锁避免死锁的几个方法:
避免一个线程同时获取多个锁;
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源;
尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制;
对于数据库锁,加锁和解锁必须在一个数据库里,否则会出现解锁失败的情况。
(1)资源限制的含义
资源限制是指进行并发编程时,程序的执行速度受限于硬件资源和软件资源。硬件资源的限制有带宽的上传、下载速度,磁盘的读写速度和CPU的处理速度;软件资源限制有数据库的连接和socket连接数等。
(2)资源限制引发的问题
代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,如果因为资源受限的原因,将某段串行的代码改为并发执行,效率反而会慢下来,因为上下文切换和资源调度都需要时间。
(3)如何解决资源限制的问题
硬件资源的限制可以通过集群的方式解决;软件资源的限制可以考虑使用资源池将资源复用。
(4)在资源限制情况下进行并发编程
根据不同的资源限制调整程序的并发度。
二、Java并发机制的底层实现原理Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
1、volatilevolatile是轻量级的synchronized,在多处理器的开发中保证了共享变量的可见性(当一个线程修改一个共享变量时,另外一个线程能够读取到这个修改的值)。
有volatile变量修饰的共享变量进行写操作的时候,CPU会加上lock前缀的汇编指令。Lock前缀的指令在多核处理器下会引发:
将当前处理器缓存行的数据写回到系统内存;
这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。
在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的数据是不是过期了。
volatile的两条实现原则:
Lock前缀指令会引起处理器缓存回写到内存
一个处理器的缓存回写到内存会导致其他处理器的缓存无效
(1)利用synchronized实现同步的基础,Java中的每一个对象都可以作为锁:
对于普通方法,锁是当前实例对象;
对于静态同步方法,锁是当前类的Class对象;
对于同步方法块,锁是Synchronized括号里配置的对象。
Synchronized在JVM里的实现原理:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,用monitorenter和monitorexit指令实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit是插入到方法的结束处和异常处。JVM要保证每个monitorenter有对应的
monitorexit配对。任何对象都有一个monitor与之关联,并且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试
获取对象所对应的monitor的所有权,即尝试获得对象的锁。
(2)synchronized用的锁是存在Java对象头里的,对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。注意32位虚拟机和64位虚拟机下Mark Word的存储结构不一样。