一、
上下文切换问题:时间片一般是几十毫秒,任务从保存到再加载的过程就是一次上下文切换,多线程竞争锁时会引起上下文切换,时间片到了会引起上下文切换
减少上下文切换:1、无锁并发线程 2、CAS算法 3、使用最少线程 4、协程
死锁问题:1、尽量避免一个线程同时获得多个锁
2、尽量避免一个线程在锁内同时占用多个资源
3、尝试使用定时锁lock.tryLock(timeout)
4、对于数据库锁,加锁和解锁必须在同一个数据库连接里
二、JVM的并发机制
1、volatile关键字:
volatile比synchronized执行成本更低,因为他不会引起线程上下文的切换和调度
被volatile修饰的共享变量进行写操作的时候会多出一条以lock为前缀的汇编指令:1、lock指令会引起会引起处理器缓冲回写到内存 2、一个处理器的缓冲回写到内存会引起其它处理器的缓冲失效
volatile关键字的作用:1、保证可见性 2、禁止指令重排序【但这两个功能的底层实现都是lock指令】
2、synchronized关键字(抛出异常自动释放锁)
所以synchronized的底层操作含义是先对对象头的锁标志位用lock cmpxchg的方式设置成“锁住“状态,释放锁时,在用lock cmpxchg的方式修改对象头的锁标志位为”释放“状态,写操作都立刻写回主内存
无所状态---->偏向锁状态---->撤销偏向锁状态---->轻量级锁状态---->重量级锁状态
偏向锁的撤销是狠耗时的
偏向锁通过偏向锁的撤销膨胀成轻量级锁·
轻量级锁通过自旋膨胀成重量级锁
3、原子操作的实现原理:(CAS操作同时具有volatile读-写的语义)
(1)、总线锁
(2)、缓冲锁(并不是都支持)
(3)、CAS就是利用lock CMPXCHG指令实现的(JAVA所用)
①ABA问题
②循环时间长开销大
③只能保证一个共享变量的原子操作
④JVM的synchronized的机制中也用了CAS的锁机制
三、JVM的内存模型
线程之间的通信方式:共享内存(JAVA所用)和消息传递
堆内存是所有线程共享的
缓冲区和重排序这两个问题
重排序:编译器重排序、指令集并行重排序、内存系统重排序【JMM会限制编译器重排序,也会通过插入内存屏障的指令的方式来禁止一些处理器重排序】
由于处理器使用了写缓冲区,所以会对storeload操作进行重排序【这个屏障会把当前所有写缓冲区的数据全部刷新到内存中】
as-if-serial[同一线程内的数据依赖性]
happen-before[多线程之间存在可见性,那么就一定存在happen-before规则]
顺序一致性模型
synchronized及临界区内的代码是可以重排序的
1、volatile语义:
严格限制volatile变量和普通变量的重排序
volatile的单个变量的读写操作具有原子性
对一个volatile变量的读,总是能看到之前最后一次对这个变量的写
volatile的写和锁的释放具有相同的内存语义
volatile的读和锁的获取具有相同的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的所有写的共享变量值刷新到主存中
当读一个volatile变量时,JMM会把该线程对应的本地内存值为无效。线程接下来将从主内从中读取所有读的共享变量
内存语义的实现:1、storestore volatile写 storeload 2、volatile读 loadload loadstore
用途:一写多读
2、锁的内存语义:
临界区互斥执行
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存值为无效,从主内从中刷新共享变量
内存语义的实现:
CAS(compareAndSwap())同时具有volatile的写和读的内存语义
3、final的内存语义:
(1)在构造函数中对final域的写入和把这个堆对象赋值给它的引用,他们之间不能重排序
(2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能进行重排序
垃圾回收器:
1、垃圾回收线程是怎么让其他用户线程到安全点都自动停止下来的?
主动式中断:不对线程操作,仅仅设置中断标志;每个线程执行时主动轮询这个标志,发现中断标志为真时,就自己中断挂起;轮询标志的地方和安全点是重合的。
2、垃圾回收线程是怎么让其他用户线程到安全区域都自动停止下来的?
当位于安全区域的线程在线程离开安全区域时,会检测系统是否已经完成了根节点的枚举,如果没有完成,则必须等待收到离开区域的信号为止。