🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**公平锁和非公平锁:** 公平锁是队列的,按顺序,非公平锁抢到就占用锁,可插队。非公平锁吞吐量比公平锁大,但是可能会出现饥饿想象(某个线程一直都拿不到锁) synchronized 和 ReentrantLock 默认都是非公平锁 **可重入锁(递归锁)** 访问的同步代码中还有同步代码,这时候内层的代码块可直接进入,使用的和外层是同一把锁。synchronized 和 ReentrantLock 是可重入锁。可防止死锁 **自旋锁:** 采用循环的方式去尝试获取锁(CAS)。优点 不会立即阻塞而且减少线程上下文切换的消耗 缺点 循环会消耗CPU,如果一直失败,对CPU影响大 **独占锁(写锁) / 共享锁(读锁)** 有一个线程在写,别的线程就没法读或写 **偏向锁** 无竞争时使用,一旦有第二个线程竞争,就会从偏向锁改成轻量级锁 ## 1. CAS 原理? 轻量级自旋锁 线程拿到的值和主内存中的值一致, 就将要写入的值写入到主内存,否则就修改失败。 使用 Java中的sun.misc.Unsafe 类 这个类的方法都是native的,完全依赖于硬件的功能,通过它实现了原子操作。CAS是一条CPU的原子指令,执行是连续的,执行过程中不允许被打断,不会造成数据不一致问题。 ## 2. CAS 缺点? 1. 循环时间长开销会很大 1. 如果CAS失败,会一直进行尝试自旋(do while 方式),如果长时间不成功,会一直循环,对cpu影响大。 ## 3. ABA问题 1. 简单来说就是 最初拿到的值和最终比较时值一样,但是这期间值实际是修改过的。 T1 T2 俩个线程从主内存中都拿到的变量是A,T1比较慢,T2 把A改成了B,并写入到主内存,又来了个T3,拿到主内存 B并改成A写入到主内存,这时候T1执行完成值改为了1,发现主内存还是A和自己当时拿到的值一致,就将1写入主内存成功。但是实际上主内存的值已经从出现过A=》B=》A的过程。就是一个线程往主内存写入数据时,发现主内存中的值和线程当时拿到的值是相同的,符合CAS的逻辑,但是实际上主内存中的这个值已经修改过多次,只能当这个线程去写入时,刚好和线程当时拿到的值一致。中间过程中值可能已经被改动过了。 ## 3. ABA 解决方式? 版本号,修改一次版本号就增加一次。比如 elasticsearch 中数据每更新一次,版本号字段就加一 ## 4. 什么是偏向锁? 在对象头中记录线程id,如果下次来的还是这个线程则直接运行后续代码。 需要启用,JVM 默认延迟 4s 自动启用 ## 5. 什么是synchronized ? 是Java中的关键字:用来修饰方法、对象实例。属于独占锁、悲观锁、可重入锁、非公平锁。 1. 作用于实例方法时,锁住的是对象的实例(this); 2. 当作用于静态方法时,锁住的是 Class类,相当于类的一个全局锁, 会锁所有调用该方法的线程; 3. synchronized 作用于一个非 NULL的对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。 ## 6. synchronized 加锁原理? jdk 1.6 之前直接就是重量级锁:通过对象内部的监视器(monitor)实现,依赖于操作系统,线程需要争抢 monitor,抢到的就是加锁成功,没抢到的会放到队列中。涉及到阻塞同步,上下文切换,线程调度,操作系统的内核态和用户态的切换。消耗大,性能差。 jdk1.6 以后对synchronized锁进行了优化 内核态:当进程(线程)需要内核代码执行时,就是处于内核态。其他都是用户态。 ## 7. synchronized 锁升级(锁膨胀)? 无锁 => 偏向锁 =》 自旋锁(轻量级锁) =》 重量级锁 当有线程加锁时,会在对象头中记录线程id,如果以后还是这个线程,则可以直接运行后续代码,当2个线程以上争抢加锁时,则直接升级为轻量级锁,CAS进行自旋加锁,当线程加锁竞争激烈,CAS自旋不成功(默认 10 次)则升级为重量级锁。 ## 可重入锁如果加了两把,但是只释放了一把会出现什么问题? 程序卡死,线程不能出来,也就是说我们申请了几把锁,就需要释放几把锁。 ## 如果只加了一把锁,释放两次会出现什么问题? 会报错,java.lang.IllegalMonitorStateException。 ## Lock和synchronized的区别? 1. Lock 需要手动获取锁和释放锁 2. Lock 是一个接口,而 synchronized 是 Java 中的关键字, synchronized 是内置的语言实现。 3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。 4. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。 5. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。 6. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。