您的位置:首页 > 编程语言 > Java开发

Java并发编程艺术笔记之一二章

2016-03-15 00:39 471 查看
一、并发编程的挑战

1.并发编程的目的->让程序跑的更快...

2.单核处理器也能支持多线程执行代码,给每个线程分配CPU时间片来实现...

3.CPU在切换到下一个任务前会保存上一个任务的状态,直到再次加载这个状态的过程称为一次上下文切换...

4.并发不一定比串行快,因为线程有创建上下文切换的开销...

5.join()是等待线程结束后,主线程再结束...

6.如何减少上下文切换?

a.无锁并发编程,把关联性不强的数据分开处理,eg:把数据ID用hash算法取模,不同线程处理不同段的数据@多线程竞争锁的时候,会引起上下文切换

b.CAS算法,Java的Atomic包使用CAS算法来更新数据,不需要加锁...

c.使用最少的线程,任务比较少,可以减少线程的使用;

d.协程,在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换...

7.死锁->两个线程都在等待对方释放资源(锁)...

8.如何避免死锁?

a.避免一个线程同时获取多个锁

b.避免一个线程在锁内占用多个资源,尽量保持每个锁只占一个资源

c.尝试使用定时锁,使用定时锁,eg:lock.tryLock(timeout)来代替使用内部锁机制

d.对于数据库锁,加锁和解锁都在一个数据库连接里面进行,否则会出现解锁失效的情况...

二、Java并发机智的底层实现原理

1.volatile是轻量级的sychronized,它在多处理器开发中保证共享变量的"可见性";

2.可见性可以理解为:一个线程修改一个共享变量时,另一个线程能读到这个修改后的值;

3.使用volatile合理的话,会比使用sychronized成本更低,因为它不会引起上下文切换和调度;

4.volatile是使用排他锁单独获取这个变量的

5.在CPU层面上,对volatile进行写操作,jvm会向CPU发送一条Lock前缀的指令:

a.Lock前缀指令会引起当前CPU缓存行的数据写回到系统内存(这里的Lock信号一般会锁缓存,不会锁总线,锁总线开销太大...);

b.这个写回内存的操作会使其他CPU里缓存该内存地址的数据失效...

为了保证各个CPU缓存是一致的,就会实现缓存一致性协议,CPU会使用嗅探技术保证内部缓存、系统内存、其他CPU缓存在总线上保持一致;

6.sychronized一般称为重量级锁,但经过多个JDK版本的优化,其实没那么重;

7.sychronized加锁的三种的情况:

a.普通同步方法,锁的是当前实例

b.静态同步方法,锁的是当前类的Class对象

c.同步方法块,锁的是sychronized括号里配置的对象;

8.sychronized同步的原理->JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,其中代码块同步是使用了monitorenter和monitorexit一对指令来实现的,monitorenter指令在编译后插入到同步代码块开始位置,monitorexit是插入到方法结束和异常处,成对出现...

9.sychronized用到的存在Java对象头中,Java对象头的Mark Word中包含:HashCode+分代年龄+锁标记位

10.Java中锁的状态分为4种,且状态只升不降

a.无锁

b.偏向锁->经过研究发现,锁不仅不存在多线程竞争,而且总是由一个线程多次获得,则线程访问同步代码块的时候,会在对象头和栈桢记录存储锁偏向的线程ID,以后该线程在进入和退出同步代码块时,不需要进行CAS操作来加解锁...

c.轻量级锁->线程在执行同步代码块时,JVM会将当前线程的栈桢中创建用于存储锁记录的空间,并在对象头中MarkWord复制到锁记录中,然后尝试使用CAS对对象头的MarkWord进行替换为指向锁记录的指针,若失败,会启用自旋锁等待获取该锁...

d.重量级锁...轻量级解锁时,会使用原子的CAS操作将Diaplaced Mark Word替换回原来的对象头,若失败了,表示锁存在了竞争,锁就会膨胀成重量级锁...

11.三种锁的特点及适用场景:

a.偏向锁,加解锁不需要额外消耗,若出现进程就会带来消耗,适用于只有一个线程访问同步代码块的场景

b.轻量级锁,竞争线程不会被阻塞,若得不到锁,使用自旋锁会消耗CPU,适用于追求响应时间,同步代码块执行块的情况...

c.重量级锁,线程竞争不用自旋锁,不消耗CPU,但是会阻塞,响应时间慢,适用于追求吞吐量,同步代码执行周期较长的情况...

12.CPU进行原子操作主要依赖于两种方式:a.锁总线 b.锁缓存(常用)

13.两种不能使用锁缓存来保证原子操作的情况:

a.当前操作的数据不能保存在CPU缓存中,或者操作数跨越了过个cacheline(缓存行->CPU缓存基本单位)

b.有些CPU不支持锁缓存...

14.Java实现原子操作->使用循环CAS实现原子操作...

15.CAS实现原子操作的三大问题:

a.ABA问题->在CAS操作时,需要检查一个值是否发生了变化,但是有的值原来是A,变成了B,之后又变成了A,此时需要引入AtomicStampedRef来解决ABA问题,其原理是引入了版本检查,即->先查是否为预期引用,并且查是否为预期标志...

b循环时间开销大(CPU消耗多)->jvm支持CPU的pause指令,可以延迟流水线执行指令,CPU不会消耗过多的资源...

c.只能保证一个共享变量的原子操作->可以把多个共享变量打包成对象,进行CAS操作...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: