1.上下文交换
减少上下文交换的方法
-
无锁并发编程:进行数据操作,多线程竞争锁,引起上下文切换。将变量按照id 进行hash,不同线程处理不同段的数据
-
CAS(compare and swap)
A线程和B线程都要修改变量X,先从内存中读取X放在各自的缓冲区。然后修改,在写回之前,先对比缓存区的和内存中的数据一样吗,一样再写回。
-
创建线程数少一点
2.死锁
充要条件:互斥、请求保持、循环等待、不可剥夺
避免死锁:避免一个线程同时获得多个锁、避免一个锁同时获得多个资源、定时锁、加锁和解锁必须在一个数据库连接中
3.并发机制的底层实现原理
(1)voliate(保障数据修改的可见性)
step1:任意线程对数据的写入立刻刷新到主内存
使用汇编命令的LOCK#信号,确保在声言该信号期间,处理器可以独占任何共享内存。锁总线或者锁缓存,一般锁缓存。为了解决锁总线的劣势,引入了MESI协议。
step2:让缓存区的数据失效。
处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。不一致缓存中的数据就失效。
(2)synchronized
Java中的每一个对象都可以作为锁,对象头中有标记位标记是否锁。
-
对于普通同步方法,锁是当前实例对象。
-
对于静态同步方法,锁是当前类的Class对象。
-
对于同步方法块{},锁是Synchonized括号里配置的对象。
代码块同步使用Monitorenter和monitorexit实现。monitorenter放在方法开始的时候,monitorexit放在方法结束和异常的地方。任何一个对象都有一个monitor与之关联。
(3)synchronized用的锁是存在Java对象头里的。
markword:存放hashcode、分代年龄、是否是偏向锁、锁类型标记位。
类型指针:
数组长度:
(4)锁的升级和对比
无锁状态、偏向锁状态、轻量级锁、重量级锁(只能升级不能降低)
**偏向锁加锁:**访问同步代码块并且获取锁的时候,就在对象头和栈帧的锁记录里面存储偏向的线程id。原来对象头存的是hashcode和分代年龄=》(变为)线程id。
释放偏向锁:等待竞争才会释放锁。等待全局安全点,看看持有偏向锁的线程还活着嘛。如果挂了就改成无锁状态,然后偏向本线程。如果线程还活着,就遍历栈帧和对象,锁记录偏向其他线程或者恢复成无锁状态、或者标记对象不适合使用偏向锁(锁升级)。
出现竞争、偏向次数太多了(锁升级)。
**轻量级锁加锁:**在线程栈创建锁空间,复制堆中对象Markword的数据,到锁空间中。多个线程通过CAS将堆中对象头的Mark word改成锁空间的地址。成功的那个线程就抢到锁了。加锁失败的线程一直自旋。阈值10升级成重量级锁。(while(true))
轻量级锁释放锁:使用原子的CAS操作将Displaced Mark Word替换回到对象头。如果失败了就是有线程在竞争锁,重新执行代码块的内容。
(5)CAS实现原子操作的三大问题
- ABA问题:A->B->A,CAS算法认为它没有变。解决思路就是使用版本号。
- 循环时间长开销大
- 只能保证一个共享变量的原子操作