JUC: 线程锁

1 面试题复盘

  • 如何理解多线程,如何处理并发,线程池有哪些核心参数?

  • Java加锁有哪几种锁?

  • synchronized原理是什么?为什么可重入?如何获取对象的锁?

  • JVM对原生锁做了哪些优化?

  • 什么是锁清除和锁粗化?

  • 乐观锁是什么?synchronized与乐观锁什么区别?

  • volatile有什么作用?

  • ReentrantLock原理是什么?

  • AQS框架原理介绍一下?

  • 简单说说Lock

  • 是否使用过CountDownLanch? 如何使用?

2 乐观锁与悲观锁

(1)悲观锁

synchronized和Lock都是悲观锁, 同一时间点,有且只有一个线程能够访问对应的资源。 写操作多的场景使用。

(2)乐观锁

认为自己在使用数据时,不会有别的线程修改数据或资源,所以不会添加锁。只是在更新资源的时候,需要去判断当前数据有没有别的线程更新过。判断规则有:

  • 版本号机制version,每一次更新一个版本号。
  • 采用CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。比较并交换

3 锁是什么

(1)锁案例演示 - synchronized的三种应用方式

// 1. 对象锁:对于非静态方法使用synchronized就是加的对象锁,获得的是这个对象(this)作为锁
public synchronized void sentEmail(){
 try {
 TimeUnit.SECONDS.sleep(4);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 System.out.println("sent email");
}
// 2. 类锁:对于静态方法或使用synchronized就是加的类锁,获得的是这个类对象(.class)作为锁
public static synchronized void sentSMS(){
 System.out.println("sent SMS");
}
// 3. 代码块,使用的是synchronized括号内的对象
public void testSynchronized(){
 synchronized (this){
 System.out.println("testSynchronized");
 }
}

(2)从字节码角度分析synchronized实现

  • javap -c ***.class文件反编译
javap -c a.class # 	​​反汇编代码​​,输出每个方法的 Java 字节码指令(指令集)
-v或 -verbose # 输出​​最详细的附加信息​​,包括版本号、常量池、方法描述符(签名)、栈大小等
  • synchronized同步代码块
public void testSynchronized(){
 synchronized (this){
 System.out.println("testSynchronized");
 }
}
public void testSynchronized();
 Code:
 0: aload_0
 1: dup
 2: astore_1
 3: monitorenter # 获取锁
 4: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream;
 7: ldc #44 // String testSynchronized
 9: invokevirtual #34 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 12: aload_1
 13: monitorexit # 释放锁
 14: goto 22
 17: astore_2
 18: aload_1
 19: monitorexit # 异常情况也可以释放锁
 20: aload_2
 21: athrow
 22: return
 Exception table:
 from to target type
 4 14 17 any
 17 20 17 any
}
  • synchronized对象锁

-v

public synchronized void sentEmail(){
 System.out.println("sent email");
}
 public synchronized void sentEmail();
 descriptor: ()V
 flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED # 会检查对象的ACC_SYNCHRONIZED是否被设置,如果设置了,则会持有monitor直到方法完成释放
 Code:
 stack=2, locals=1, args_size=1
 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
 3: ldc #13 // String sent email
 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 8: return
 LineNumberTable:
 line 31: 0
 line 32: 8
 LocalVariableTable:
 Start Length Slot Name Signature
 0 9 0 this Lcom/thread/sgg/juc/Phone;
  • synchronized类锁
public static synchronized void sentSMS(){
 System.out.println("sent SMS");
}
 public static synchronized void sentSMS();
 descriptor: ()V
 flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED # 根据是否有ACC_STATIC是否存在判断应该是类锁还是对象锁
 Code:
 stack=2, locals=0, args_size=0
 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
 3: ldc #21 // String sent SMS
 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 8: return
 LineNumberTable:
 line 34: 0
 line 35: 8

(3)反编译synchronized锁的是什么

为什么任何一个对象都可以成为一个锁?

Object是任何类的父类

什么是管程monitor?

monitor是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。

这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

//结构体如下
ObjectMonitor::ObjectMonitor() { 
 _header = NULL; 
 _count =