Appearance
1.你了解乐观锁和悲观锁吗?
- 悲观锁:每次读数据都会上锁,如synchronized
- 乐观锁:只有在更新时判断版本号是否正确
- 悲观锁适合写多,乐观锁适合读多吐量比悲观锁高
2.什么是可重入锁/不可重入锁?
- 可重入锁:也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
- 不可重入锁:当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
例如我们使用mounthA,如果使用可重入锁mounthB就能获取到,使用不可重入锁mounthB就会阻塞。
java
private void mounthA(){
// TODO 获取锁
mounthB();
}
private void mounthB(){
// TODO 获取锁
//其他操作
}
3.什么是公平锁/非公平锁?有什么特点?使用场景?
- 公平锁:多个线程顺序排队获取锁,保证每个线程都有机会获取资源,吞吐量可能会降低;因为处理第一个线程其他的都会阻塞。
- 非公平锁是:多个线程同时尝试获取锁,获取不到则进入队列等待;这种方式可以减少唤醒线程的开销,提高吞吐量,但可能导致某些线程长时间无法获得锁,产生饥饿现象。
4.什么是排他锁和共享锁?
- 共享锁:不允许写入,只允许查看,可被多个线程持有
- 排他锁:又称互斥锁/写锁/独占锁,每次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁
5.说一下JVM中的偏向锁、轻量级锁和重量级锁
- 添加线程睡眠会从无锁变为匿名偏向锁
- 使用synchronized锁住对象,会从匿名偏向锁升级为偏向锁
- 如果多个线程同时竞争(CAS方式)synchronized修饰的代码块会升级为轻量级锁
- 如果CAS自旋时间过长仍获取不到资源,会升级为重量级锁
6.synchronized升级过程中有几次自旋?
- 获取已被其他线程持有的轻量级锁,会CAS自旋等待其释放
- 获取已被其他线程持有的重量级锁,会CAS自旋等待其释放(JDK8默认情况下不会开启重量级锁自旋)
7.synchronized的锁优化是怎样的?
- 自旋锁:用于快速接手刚释放的锁,可以避免普通线程挂起等待,提高效率;大多情况下锁的持有者很快就会释放锁,因此采用传统的线程挂起等待锁释放后恢复的方法效率较低。
- 锁消除:对于一些局部的同步代码块,jvm确定其不会被其他线程访问掉,会在JIT编译时取消同步。
- 锁粗化:如果在一段代码中连续的对同一个对象反复加锁解锁,编译时会释放放款加锁范围。
锁消除如下所示,hello对象只存于f()内,不会被其他线程访问到:
java public void f() { Object hello = new Object(); synchronized(hello) { System.out.println(hello); } }
锁粗化如下所示:
java
for(int i=0;i<100000;i++){
synchronized(this){
do();
}
// 会被粗化成
synchronized(this){
for(int i=0;i<100000;i++)
do();
}
7.解释一下什么是死锁?你能举一个死锁的例子吗?
多线程在互相竞争资源时都没有办法获取到资源出现的一种状况,若无外力阻止将永久阻塞下去,生产环境如果出现死锁非常难以排查,因为他不是必然出现的,死锁需要满足四个条件才能触发。
例如如下代码,在锁A申请锁B,锁B申请锁A,在微观角度如果同时执行就会发送死锁,因为methodA执行后由于methodB也执行了,导致methodA内获取不到LOCK_B。
java
public class Test2 {
private static String LOCK_A = "lock_a";
private static String LOCK_B = "lock_b";
public void methodA() {
synchronized (LOCK_A) {
System.out.println("我是A方法中获得了锁A " + Thread.currentThread().getName());
synchronized (LOCK_B) {
System.out.println("我是A方法中获得了锁B " + Thread.currentThread().getName());
}
}
}
public void methodB() {
synchronized (LOCK_B) {
System.out.println("我是B方法中获得了锁B " + Thread.currentThread().getName());
synchronized (LOCK_A) {
System.out.println("我是B方法中获得了锁A " + Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
System.out.println("主线程运行开始运行:" + Thread.currentThread().getName());
Test2 test2 = new Test2();
// 使用for循环的原因是,死锁并不一定会触发
for (int i = 0; i < 20; i++) {
new Thread(test2::methodA).start();
new Thread(test2::methodB).start();
}
System.out.println("主线程运行结束:" + Thread.currentThread().getName());
}
}