面试【JAVA基础】锁


一、锁状态

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

  • 无锁

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

  • 偏向锁

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

  • 轻量级锁

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

  • 重量级锁

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

二、乐观锁与悲观锁

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

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

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

优势:线程不进行上下文切换,减小了上下文切换的时间。
存在的问题:若是线程持有锁的时间较长,其余线程进入循环,消耗cpu。spa

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

四、公平锁与非公平锁

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

五、重入锁与非重入锁

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

六、共享锁与排他锁

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

七、读写锁

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

八、CAS

CompareAndSwap比较与交换,是一种无锁算法,原子类使用了CAS实现了乐观锁。
带来的问题:线程

  1. ABA问题

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

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

能够把多个变量放在一个对象里面进行CAS操做。code

九、锁优化

9.一、锁升级
  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.二、锁粗化

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

9.三、锁消除

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

十、synchronized底层实现

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

十一、synchronized与ReentrantLock的区别

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

十二、volatile关键字

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

volatile和synchronized的区别:

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

1三、Atomic原子类实现

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

1四、AQS

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

1五、AQS的组件

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

countDownLatch和CyclicBarrier的区别

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

1六、锁降级

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

1七、逃逸分析

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

tencent.jpg

相关文章
相关标签/搜索