Java并发编程的艺术-----第二章读书笔记
1.1
原文:时间片是CPU分配给各个线程的时间。P1
时间片是CPU分配给各个线程的时间,一般是几十毫秒。
原文:上下文切换P1
任务从保存到再加载的过程是一次上下文切换。一个任务可能没有执行完毕就释放了锁,再次争抢同步锁并读取线程内的私有工作内存需要一定的时间,或者说是性能消耗。
1.1.2
并发执行的速度有时不如串行,原因是线程有创建与上下文切换的开销。P3
1.1.3
原文:如何减少上下文切换?P3
使用CAS操作进行无锁并发编程。多线程竞争锁的时候,会引起上下文切换。P3
任务为了恢复上一次的工作状态的时候,需要先争抢到同步锁。所以使用无锁并发编程可以减少上下文切换。Java可以使用atomic包下的原子操作类,它们都是使用的CAS操作,可以避免使用同步锁。
另外一点就是避免创建过多的线程。每一个WAITING状态的线程切换到Runnable状态都会进行一次上下文切换。
实战:通过查看dump线程信息,发现pid为3117的进程里面有300多个线程处于WAITING状态,所以调整JBOSS的线程池。
1.2
编程中避免死锁的办法P6
避免一个线程同时获得多个锁(ReentrantReadWriteLock的锁降级除外)
避免一个线程同时占用过多的资源,因为一些资源不释放,其它线程也不能使用。
使用定时锁。如果在一定时间内获取不到锁,那么就放弃操作,比如Lock接口定义的 tryLock(timeout)方法
可能出现死锁的情况:(1)出现死循环(2)释放锁的时候,出现异常。
注:死锁的四个必要条件(1)互斥条件(2)请求和保持条件(3)不可剥夺条件(4)环路等待条件
2.1
原文:volatile变量修饰符比synchronized的的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。P8
即可以回答 volatile对比synchroinzed的优势:
原来volatile可以不引起上下文调度,所以成本低。线程无须从私有内存中恢复变量的值,直接从公共堆栈中获取值即可。
volatile关键字的致命缺点:不能保证原子性
volatile关键字原理:
(1)将最先的变量的值写入到主内存中
(2)使其它线程中缓存了该变量的内存地址失效
2.2
synchronized关键字原理:JVM基于进入和退出监视器Monitor对象来实现方法同步和代码块同步。P12
在JVM中,将监视器进入指令插入到同步代码的开始位置,将监视器退出指令加入到同步代码的结束或者异常位置。
2.2.1
synchronized用的锁是存在Java对象头里的。P12
锁信息存放在对象头中,对象头用来存储锁状态信息与hashcode以及对象类型。
Mark Word用来存储对象的hashCode或锁信息。
2.2.2
锁一共有4中状态,级别从低到高分别是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。P13
锁升级不能降级的策略,目的是为了提高获取锁和释放锁的效率。 P13
偏向锁:当一个线程访问同步块并获取锁的时候,会在对象头和栈帧中的锁记录里面存储锁偏向的线程ID,以后该线程在进入和退出同步块时,不需要进行CAS操作来加锁和解锁,只需简单的测试一下对象头里的Mark Word里面是否存储这指向当前线程的偏向锁。P13
在没有竞争同步锁的并发环境里,锁的状态是偏向锁。线程在获取锁后,锁对象的对象头里面会记录此持有锁的线程的ID。当线程再次访问其它同步代码的时候,不需要进行CAS操作来进行加锁和解锁,这样就降低了性能的消耗。
一个线程获取了锁以后,那么锁会变成偏向锁。锁对象的对象头会记录住持有锁的线程ID。当这个线程再去访问其它同步代码块的时候,只需要简单的测试一下锁的对象头里是否还是指向的这个线程。如果是,不需要进行CAS操作来进行加锁和解锁,这样就降低了性能的消耗。那么当前线程就可以进入同步代码块。
偏向锁使用了一种等到竞争才释放锁的机制,所以当其它线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放锁。P13
什么时候释放偏向锁?有其它线程竞争偏向锁的时候,偏向锁会膨胀为轻量级锁。
轻量级锁:线程在执行同步块之前,JVM会现在当前线程的栈帧创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称之为 Displaced Mark Word。然后线程尝试使用CAS将锁的对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获取锁。如果失败,表示有其它线程竞争锁,当前线程尝试使用自旋来获取锁。P15
(1)将锁对象的对象头复制到线程的新创建的锁记录空间中
(2)线程尝试将锁的对象头指向当前线程的锁记录空间
(3)如果指向成功,线程获取锁。如果指向失败,线程自旋。
(4)如果自旋获取锁成功,说明另外一个线程不会与当前的线程发生冲突。如果自旋获取锁失败了,那么当前的同步锁膨胀成重量级锁。
重量级锁:其它线程在获取同步锁失败后,将不会自旋,直接进入阻塞状态。
偏向锁的优缺点:加锁与解锁不需要带来额外的消耗。如果线程间存在竞争,将会带来额外的锁撤销的性能消耗。适用于只有一个线程的同步场景。
轻量级锁的优缺点:竞争的线程不会阻塞,将会尝试用自旋获取锁,提高了程序的响应速度。但如果自旋一直获取不到锁,自旋将会消耗额外的性能。适用于追求响应速度的场景。
重量级锁的优缺点:线程竞争不使用自旋,不消耗性能。但是线程会阻塞,程序响应速度慢。适用于追求吞吐量和任务时间长的场景。
2.3
原子操作:不可被中断的操作。
Java是如何实现原子操作的:循环执行CAS操作,直到成功为止。
ABA问题:使用带版本号(时间戳)的原子操作类来避免。
for(;;){
int currentValue = getCurrentValue();
boolean succ = automicInteger.compareAndSet(currentValue,++currentValue);
if(succ){
break;
}
}
.