1.Synchronized用过吗?讲一下它有什么用。
synchronized是Java中的一个关键字,主要用来解决多线程之间访问共享资源出现的同步安全性问题的。它可以保证被它修饰的对象、方法、代码块在同一时间点只能有一个线程访问。它是一种独占式的悲观锁,是由JVM底层实现的使用互斥同步方式。
追问:说一下你平时都是怎么用synchronized的,它有哪些用法?
synchronized关键字主要用三种使用方式:
- 修饰实例方法:相当于给当前类的实例加锁,作用于当前类的实例;
- 修饰静态方法:相当于给当前类加锁,作用于当前类。因为静态成员不属于任何一个实例对象,而是属于当前类的;
- 修饰代码块:相当于给当前类的实例加锁,跟修饰实例方法差不多,只是锁的作用范围有所不同,被锁修饰的同步代码块前后的代码不会被加锁,只是锁住一部分代码;
2.既然你用过synchronized,那你一定了解它的底层原理咯?讲一下吧!
可以通过JDK自带的javap命令去查看被synchronized修饰程序执行时候的字节码文件。查看字节码文件可以发现编译器生成了monitorenter和monitorexit这两个字节码指令,monitorenter指令指向同步代码块开始的位置,monitorexit指令指向同步代码块结束的位置。当虚拟机执行monitorenter指令时,线程会试图获取对象的锁,如果这个对象没有被其他线程锁定,或者当前线程已经拥有这个对象的锁,那么成功获取对象的锁,锁的计数器会+1;当执行monitorexit指令时,锁的计数器会-1,直到计数器为0的时候表明锁已经被释放。如果线程获取锁对象失败了,那么就会被阻塞,直到对象锁被拥有它的线程所释放为止。
3.说一下什么是可重入性,为什么synchronized是可重入锁?
可重入心是锁的一个基本特性,是为了解决自己锁死自己的情况。对于synchronized来说,可重入性是显而易见的,虚拟机在执行monitorenter指令的时候,如果对象没有被任何线程锁定,或者已经被当前线程锁定,那么就会将锁的计数器+1,表示再一次获取到锁,而不是获取过一次就不允许再获取了,锁的计数器也是实现可重入性的本质。
4.为什么说synchronized是非公平锁?
非公平主要表现在获取锁的行为上面,线程获取锁的顺序并不是按照申请锁的顺序给线程分配锁,每当锁被释放之后,所有的线程都有资格去竞争锁,这样做的目的是为了提高程序的执行性能,缺点就是这样会造成饥饿现象。
追问:什么是饥饿现象?
一个或多个线程因为一直争抢不到锁而导致一直无法被执行。
5.什么是锁消除?什么是锁粗化?
- 锁消除:虚拟机即时编译器运行的时候,对一些代码要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除,锁消除的主要判定依据来源一逃逸分析的数据支持。锁消除是发生在编译器级别的一种锁的优化方式;
- 锁粗化:原则上,同步代码的作用范围越小越好,但是如果一系列连续的操作都需要对同一个对象进行频繁的获取锁和释放锁操作,这种频繁的同步互斥操作会导致不必要的性能开销。锁粗化就是增大锁的作用范围,减少对同一个对象获取锁和释放锁的次数;
注:如果有小伙伴有兴趣想了解逃逸分析的可以点击这里逃逸分析
6.说一下synchronized关键字在JDK1.6之后主要做了哪些优化?
大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且还总是由同一个线程多次获取锁。在JKD1.6之前如果要将一个线程进行阻塞或者唤醒操作都需要操作系统协助,需要在用户态和内核态之间相互切换,这样非常消耗性能,所以在JDK1.6开始官方对synchronized做了很多优化。
JDK1.6中对synchronized的实现引入了大量技术进行优化,比如偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等技术减少来减少获取锁和释放锁的性能开销。
优化之后锁的状态主要有四种,无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几种状态会随着锁的竞争程度进行升级,但是不可降级,这样大大提高了获取锁和释放锁的效率。
具体优化过程,以及各个状态之间是如何升级的,可以点击这里Java并发编程(二)之synchronized
7.synchronized和ReentrantLock有什么区别?
- 两者都是可重入锁;
- synchronized依赖于JVM实现,ReentrantLock依赖于API实现;
- synchronized不可以手动释放锁,ReentrantLock可以手动释放锁;
- 与synchronized相比,ReentrantLock增加了一些高级功能,比如线程的中断与等待、实现公平锁以及实现多个线程之间的通信;
- ReentrantLock比synchronized更加灵活;
注:关于悲观锁、乐观锁以及CAS等不了解的小伙伴,可以点击这里Java并发编程(三)之悲观锁与乐观锁
8.synchronized有哪些核心组件?
- Wait Set:存储因为被调用wait()方法被阻塞的线程;
- Contention List:竞争队列,所有请求锁的线程被存放在这里;
- Entry List:Contention List中那些有资格争抢锁的线程被存放在这里;
- OnDeck:任意时刻,最多只能有一个线程正在竞争锁资源,该线程被成为OnDeck;
- Owner:当前已经获取锁的线程被称为Owner;
- !Owner:当前以及释放锁的线程;