java并发编程基本原理
========================
1.1上下文切换
单核cpu通过并发实现并行上下文切换需要保存上一个任务的状态,频繁的上下文切换会影响执行速度;
因此多线程不一定快
如何减少上下文切换
- 无锁并发编程·
竞争锁时会发生上下文切换
,如使用不同线程处理数据分片; - 使用CAS算法,Java的Atomic包使用CAS算法更新数据不需要加锁;
- 协程,在单线程中实现线程调度;
1.2 死锁
两个线程相互等待对方释放自己所需要的锁
2.1 volatile的应用
定义: java编程语言允许线程访问共享比那辆为了确保共享变量能被准确和一致的更新,线程应该确保线程通过使用排他锁获得这个变量;
如何现实?? X86下JIT编译器会将volatile写操作前添加lock 指令前缀;下面是两个实现原则;
1) lock前缀会引起处理器缓存写回到内存;
2) 一个处理器缓存写入内存会使其他处理器缓存失效;
JDK7 中的并发队列集合类 LinkedTransferQueue中将队头队尾中间插入64个字节防止头尾一起在同一缓存行锁定;
2.2 synchronized实现原理
synchronized用的锁时对象头中的,数组类型对象头占3个字宽字宽=操作系统位数
第三个字宽存储元素个数;非数组对象对象头两个字宽;
Mark Word 32/64bit
| Class Metadata Address 指向Class的指针 32/64bit
| 数组长度 32/64bit
线程在执行同步代码块时会在栈帧中创建用来存放锁记录的空间 将对象头中的Mark Word复制到记录中 官方称作Displaced Mark Word
;
1 偏向锁(低代价)
研究发现:一般情况下锁不存在竞争;而且总是由同一线程获得;
偏向锁会获取时会在mark word 中记录当前线程ID以后线程进入或者退出代码块的时候不用使用CAS算法加锁和解锁,只需要对比是否有ID;获取不到则查看是否是偏向锁;如果是则使用CAS竞争锁
锁撤销:偏向锁只有发生锁竞争时才会释放锁 ,偏向锁的撤销需要等待全局安全点;检查尺有锁线程
1)如果线程不活跃,将对象头设定为无锁状态;
2) 活跃 ,先执行偏向锁线程栈; 遍历锁记录;要么指向其他线程,要么设定无锁状态;
轻量级锁
加锁:CAS算法将对象头mark word 设置为指向锁记录的指针 成功获取锁,失败自旋;
解锁: CAS将Displaced Mark Word替换回对象头;如果失败锁升级——重量锁;
重量锁
重量锁不会降级;重量锁代码块不使用CAS通过阻塞同步;
2.3 原子操作的实现原理
处理器实现原子操作:
1)总线锁定
2)缓存锁定 ==== 有些处理器不支持
java 如何实现原子操作:
1) 锁
2) CAS算法 ; ABA问题 (可以添加版本号解决) 自旋开销大 只能有一个共享变量
3.1 java内存模型 JMM
线程之间通信两种机制分别是 共享内存隐式通信
:消息传递
栈中存放本地变量副本;堆空间共享
线程A 将本地更新过的数据刷新到共享内存 线程B 读共享内存 隐式完成线程通信;
关于指令重排序
1) 编译器优化重排序
2) 指令级并行重排序
3) 内存系统重排序
后两种属于处理器重排序
对于编译器JMM会禁止特定的重排序
对于处理器重排序 JMM会要求编译器生成字节码时插入内存屏障
PA happens-before PB ; PA 操作结果对PB可见
as-if-serial ;不管如何重排序 不影响结果;
JMM在具体实现上的基本方针:不改变(正确同步的)程序执行结果的前提下,尽可能的为编译器和处理器优化打开大门
volatile 读写
在写一个volatile变量时,JMM会把对应的本地内存中的共享变量刷新到共享内存;
当读一个volatile变量时,JMM会把线程对应的本地内存置为无效,从共享内存中读取;
如何实现volatile
1)通过在每个volatile变量的读写前后添加屏障;函数返回之前添加屏障 限制指令重排序
2)编译器会优化无效的屏障
锁的内存语义
当线程获取锁时会将线程对应的本地内存置为无效,必须重主内存中读取共享变量;
锁释放:JMM会把对应的本地内存中的共享变量刷新到共享内存
对比volatile 锁释放与volatile写 等价 锁获取与volatile读等价
A线程释放锁 :通知下一个获取这个锁的线程已经完成共享变量修改;
B线程获得锁: 接受已修改信息;
A释放B获得:实际就是在通信;
锁的实现一般模式:
1) 声明volatile共享变量
2) CAS原子条件更新同步
3) 配合volatile读写与CAS算法实现线程通信
单例模式
// volatile 解决方案
public class SafeDoubleCheckedLocking{
private volatile static Instance instance;
public static Instance getInstance(){
if(instance ==null){
synchronized(SafeDoubleCheckedLocking.Class){
if(instance == null)
instance = new instance();
}
}
return instance;
}
}
// 类初始化解决方案
public classs InstanceFactory{
private static class InstanceHolder{
public static Instance instance = new Instance(); // 声明变量
}
public static Instance getInstance(){
return InstanceHolder.instance;
}
}
class 被加载后,且被线程使用之前 类会实现初始化,JVM会获取一个锁这个锁用来同步多线程对同一个类的初始化 ;有一表示初始化状态的变量 state;每次获取锁读读变量如果已初始化则直接使用;
nstance getInstance(){
return InstanceHolder.instance;
}
}
class 被加载后,且被线程使用之前 类会实现初始化,JVM会获取一个锁这个锁用来同步多线程对同一个类的初始化 ;有一表示初始化状态的变量 state;每次获取锁读读变量如果已初始化则直接使用;
**越是追求性能的语言,内存模型设计越弱;** **越是追求可编程性的语言,语言内存模型设计越强;**