一、基础
1、为什么要并发编程
- 充分利用多核CPU的资源
2、并发编程存在的问题
- 上下文切换:PU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
- 线程创建和上下文切换的带来而外的开销
- 线程安全
- 死锁:线程A持有资源,线程B持有资源;他们都想申请对方的资源,这两个线程就会相互等待而进入死锁状态。(互相等待对方释放锁)
- 资源限制
3、并发编程三要素-如何保证多线程的运行安全
- 原子性:一个或多个操作要么同时成功要么同时失败(原子类、synchronized、lock)
- 可见性:一个线程对共享变量修改,另一个线程能够立即看到(volatile、synchronized、Lock)
- 有序性:程序执行顺序按照代码的先后顺序执行(volatile、Happens-before)(处理器可能指令重排)
4、并发和并行的区别
- 并发:多个线程操作同一资源,CPU快速交替
- 并行:多个处理器同时处理多个任务
5、同步方法和同步块,哪个是更好的选择
-
同步块是更好的选择,因为它不会锁住整个对象。同步方法会锁住整个对象
-
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象
-
同步的范围越小越好
6、synchronized 底层实现原理
-
同步方法块使用的是monitorenter和monitorexit,monitorenter指令指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置;线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁
-
同步方法:使用ACC_SYNCHRONIZED标识,指明该方法是一个同步方法
-
本质是对一个对象的监视器( monitor )进行获取,而这个获取过程是排他的,也就是同一 时刻只能有一个线 程 获 取到由 synchronized 所保护对象的监视器
-
monitor对象存在每个java对象的对象头中
7、synchronized可重入原理
- 重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
8、synchronized锁升级
- 锁的升级的目的:锁升级是为了减低了锁带来的性能消耗
- Synchronized 在jdk1.6 版本之前,是通过重量级锁的方式来实现线程之间锁的竞争。之所以称它为重量级锁,是因为它的底层底层依赖操作系统的Mutex Lock 来实现互斥功能。Mutex 是系统方法,由于权限隔离的关系,应用程序调用系统方法时需要切换到内核态来执行。这里涉及到用户态向内核态的切换,这个切换会带来性能的损耗。
- 偏向锁,就是直接把当前锁偏向于某个线程,简单来说就是通过CAS 修改偏向锁标记,这种锁适合同一个线程多次去申请同一个锁资源并且没有其他线程竞争的场景。
- 轻量级锁也可以称为自旋锁,基于自适应自旋的机制,通过多次自旋重试去竞争锁。自旋锁优点在于它避免避免了用户态到内核态的切换带来的性能开销。
- synchronized 会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏向锁,表示加锁成功直接返回。如果竞争锁失败,说明当前锁已经偏向了其他线程。需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数去尝试抢占锁资源,如果在轻量级锁状态下还是没有竞争到锁,就只能升级到重量级锁,在重量级锁状态下,没有竞争到锁的线程就会被阻塞,线程状态是Blocked。
9、synchronized锁优化
锁存在四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,随竞争而升级,不可降级。
- 偏向锁:据统计,大部分锁80%的时间只有一个线程获取。当一个线程首次获得锁时,它会在对象头中记录下这个线程的ID,这样下次这个线程再尝试获取锁时就可以无阻塞地直接获取。
- 锁撤销:需要等待全局安全点
- 轻量级锁:自旋获取锁
- 自旋锁:如果一个线程尝试获取该锁,它会一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。默认十次
- 适应性自旋锁:一种特殊的自旋锁,其自旋的时间不再固定,而是根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
- 锁消除:虚拟机在编译的时候检测到共享数据不可能存在竞争,执行锁消除
- 锁粗化:频繁的加锁和解锁会造成性能损失,把锁同步的范围扩大。
10、volatile的作用
- 可见性:当一个线程修改了一个volatile变量的值,会将这个值刷新到主内存中,并且会使用某种机制通知其他线程该变量发生变化。其他线程需要读取这个变量时,则会去主内存中读取最新的值。volatile的底层原理其实就是通过lock信号和MESI协议通知所有的处理器缓存失效,并且把数据更新到了内存。
- 有序性:happens-before
11、synchronized 和 volatile 的区别是什么?
- synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
- volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
- volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。