转载

面试【JAVA基础】锁

面试【JAVA基础】锁

1、锁状态

锁的状态只能升级不能降级。

  • 无锁

没有锁对资源进行锁定,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。其他修改失败的线程会不断重试,直到修改成功,如CAS原理和应用是无锁的实现。

  • 偏向锁

偏向锁是指一段同步代码一直被一个线程访问,那个该线程会自动获取锁,降低获取锁的代价。

  • 轻量级锁

是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。通过cas操作和自旋来解决加锁问题,自旋超过一定的次数或者已经有一个线程在自旋,又来一个线程获取锁时,轻量级锁会升级为重量级锁。

  • 重量级锁

升级为重量级锁,等待锁的线程都会进入阻塞状态。

2、乐观锁与悲观锁

  1. 乐观锁,每次拿数据的时候认为别人都不会修改,在更新的时候再判断在此期间有没有更新数据,可以使用版本号等机制,适合读取多场景,提高性能。
  2. 悲观锁,每次拿数据都认为别人会修改,都会上锁,可以使用synchronized、独占锁Lock、读写锁等机制,适合写多的场景,保证写入操作正确。

3、自旋锁与适应性自旋锁

  • 自旋锁:指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断判断锁是否能获取成功,直到获取到锁才退出循环。

优点:线程不进行上下文切换,减少了上下文切换的时间。

存在的问题:如果线程持有锁的时间较长,其他线程进入循环,消耗cpu。

  • 自适应自旋锁:指的是自旋的时间不固定,由前一个在同一个锁上自旋的时间和锁拥有者的状态来决定。如果在同一个对象上,刚刚通过自旋成功获取过锁,且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋很有可能再次成功。反之自旋操作很少成功获取锁,那么后面获取这个锁可能直接省略掉自旋的过程,直接阻塞线程。

4、公平锁与非公平锁

  1. 公平锁是指多个线程按照申请锁的顺序直接进入队列排队,队列中的第一个线程才能获取锁。
  2. 非公平锁是指线程先尝试获取锁,获取不到进入队列中排队,如果能获取到,则无需阻塞直接获取锁。

5、重入锁与非重入锁

重入锁:同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,前提是锁对象是相同的。

6、共享锁与排他锁

  1. 共享锁是指一个锁可以被多个线程锁持有。
  2. 排它锁或者叫独享锁或者互斥锁 指锁一次只能被一个线程所持有。

7、读写锁

  1. 读锁是共享的,写锁是独占的。
  2. 读读之间不会互斥,读写互斥,写写互斥,读写锁提高了读的性能。

8、CAS

CompareAndSwap比较与交换,是一种无锁算法,原子类使用了CAS实现了乐观锁。

带来的问题:

  1. ABA问题

解决思路在变量前面加版本号,每次变量更新的时候都将版本号+1,每次更新的时候要求版本>=当前版本(AtomicStampedReference)

  1. 循环时间长开销大,CAS操作如果长时间执行不成功,会导致其一直自旋,cpu消耗大。
  2. 只能保证一个共享变量的原子操作。

可以把多个变量放在一个对象里面进行CAS操作。

9、锁优化

9.1、锁升级

  1. 偏向锁的升级

线程A获取锁对象时,会在java对象头和栈帧中记录偏向的线程A的id,线程A再次获取锁时,只需要比较java头中的线程id与当前Id是否相等,如果一致则无需通过cas加锁解锁。如果不一致,说明有线程B来获取锁,那么要判断java头中偏向锁的线程是否存活,如果没有存活,锁对象被置为无锁状态,线程B可将锁对象置为B的偏向锁。如果存活,则查看A是否还需要继续持有对当前锁,如果不需要持有,则将锁置为无锁状态,偏向新的线程,如果还继续持有锁对象,则暂停A线程,撤销偏向锁,将锁升级为轻量级锁。

  1. 轻量级锁的升级

线程A获取轻量级锁时会把锁的对象头复制到自己的线程栈针中,然后通过cas把对象头中的内容替换为A所记录的地址。此时线程B也想获取锁,发现A已经获取锁,那么线程B就自旋等待。等到自旋次数到了或者线程A正在执行,线程B自旋等待,此时来了线程C来竞争锁对象,这个时候轻量级锁就会膨胀为重量级锁。重量级锁会把未获得到锁对象的线程全部变为阻塞状态该,防止cpu空转。

9.2、锁粗化

将多个连续的加锁,解锁操作连接在一起,扩展成为一个范围更大的锁,避免频繁的加解锁操作。

9.3、锁消除

通过逃逸分析,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁。

10、synchronized底层实现

  • synchronized通过Monitor实现同步,Monitor依赖于底层操作系统互斥锁来实现线程同步。
  • java对象头是由markword(标记字段)和klass point(类型指针)组成。markword存储对象的hashcode,分代年龄和锁标志位信息。Klass point 指向对象元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。
  • synchronized修饰同步代码块,是使用monitorenter和monitorexit来控制的,通过java对象头中的锁计数器。
  • 修饰方法时会将方法标识为ACCSYNCHRONIZE,JVM通过这个标志来判断方法是不是同步方法。

11、synchronized与ReentrantLock的区别

  1. 两者都是悲观锁,可重入锁。
  2. ReentrantLock 可中断,可以实现公平锁,可以绑定多个条件。
  3. ReentrantLock需要显示的调用锁和释放锁,synchronized属于java关键字,不需要显式的释放。

12、volatile关键字

  1. 保证变量内存可见。
  2. 禁止指令重排序。

volatile和synchronized的区别:

  • volatile不会阻塞,synchronized会阻塞。
  • volatile保证数据的内存可见性但不能保证原子性,synchronized两者都能保证。
  • volatile主要解决变量在线程之间的可见性,而synchronized主要解决多线程访问资源的同步性。

13、Atomic原子类实现

使用cas操作 + volatile + native方法保证同步。

14、AQS

AQS(AbstractQueuedSynchronizer)内部维护的是一个FIFO的双向同步队列,如果当前线程竞争锁失败,AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时在阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点线程。使用内部的一个state来控制是否获取锁,当state=0时表示无锁状态,state>0时表示已经有线程获取了锁。

15、AQS的组件

  1. semaphore 可指定多个线程同时访问某个共享资源。
  2. countDownLatch 一个线程A等待其他线程执行完成之后才继续执行。
  3. cyclicBarrier 一组线程等待至某个状态之后同时执行。

countDownLatch和CyclicBarrier的区别

  1. countDownLatch是一个线程等一组线程执行完成之后才执行, cyclicBarrier是一组线程互相等待至某个状态之后,同时执行。
  2. countDownLatch不能复用,cyclicBarrier可以重用。

16、锁降级

锁降级是指将写锁降级为读锁,这个过程就是当前线程已经获取到写锁的时候,再获取到读锁,随后释放写锁的过程,这么做的目的为的就是保证数据的可见性。

17、逃逸分析

  1. 逃逸分析就是分析对象的动态作用域,当一个对象在方法中被定义后,他可能被外部方法所引用,作为参数传递到其他方法中,成为方法逃逸,赋值给类变量或者可以被其他线程访问的实例变量成为线程逃逸。
  2. 使用逃逸分析,编译器可以对代码做优化。比如:同步省略(锁消除),将堆分配转化为栈分配,标量替换。
  3. 使用逃逸分析的缺点,没法保证逃逸分析的性能一定高于其他性能。极端的话经过逃逸分析后,所有的对象都逃逸了,那么逃逸分析的过程就浪费了。

面试【JAVA基础】锁

原文  https://segmentfault.com/a/1190000021330616
正文到此结束
Loading...