面试中都离不开java锁,本文巩固一下主流锁的基本概念~后续再深入源码~

Java提供了种类丰富的锁:悲观锁、乐观锁、可重入锁、非可重入锁、自旋锁、公平锁、非公平锁、无锁、共享锁、拍他锁、偏向锁、重量级锁、轻量级锁
当面试官闻到:聊聊你对锁的理解~该如何回答呢?
首先分类:

再者对比:
一:悲观锁 vs 乐观锁
这两种锁是一种广义上的定义,首先来说概念:
悲观锁:对于同一个资源进行多线程操作的时候,悲观锁认为它操作的数据会被其他线程来修改数据,因此在获取数据时就加锁确保其他线程不能修改数据。
synchronized关键字和Lock的实现类都是悲观锁。

乐观锁:在获取数据的时候不会加锁,只是修改的时候判断自己拿到的数据是否被修改,没有修改则更新,如果有修改则根据不同的实现有不同的操作。
java.util.concurrent包中的原子类都是乐观锁,最常采用的是CAS算法

悲观锁:适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁:适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
二:公平锁 vs 非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。

优点:是等待锁的线程不会饿死。按顺序获取锁
缺点:是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁:是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。

优点:可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁
三:可重入锁 vs 非可重入锁
可重入锁:是指在同一个线程在外层方法获取锁的时候,再进入该线程的子方法会自动获取锁。可一定程度避免死锁,不会因为之前已经获取过还没释放而阻塞
Java中ReentrantLock和synchronized都是可重入锁

不可重入锁:相对于可重入锁而言,当前线程在调用子方法时需要释放当前锁,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁
例如:执行方法a时,a方法获取锁之后,需要调用子方法b,但是此时该对象的对象锁已经被方法a拿走了,子方法b得不到需要的对象锁,只能等待方法a将对象锁释放,而a方法又必须调用完子方法b才能释放对象锁。

四:共享锁 vs 排他锁
共享锁:指该锁可被多个线程所持有。
排他锁:是指该锁一次只能被一个线程所持有。
synchronized和JUC中Lock的实现类就是排他锁。
五:自旋锁
众所周知:阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间
自旋锁:请求获取锁的线程自旋不释放cpu时间,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。
六:偏向锁、重量级锁、轻量级锁、无锁
这四种锁是指锁的状态,专门针对synchronized的。
| 状态 | 存储内容 | 优点 | 缺点 |
| 偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) | 加锁、释放锁不需要额外的消耗 | 线程间存在锁竞争时,会带来额外的锁撤销的消耗 |
| 轻量级锁 | 指向栈中锁记录的指针 | 竞争线程不会阻塞 | 使用自旋消耗cpu |
| 重量级锁 | 指向互斥量(重量级锁)的指针 | 线程竞争不使用自旋,不消耗cpu | 线程阻塞,响应时间慢 |
结语
限于篇幅、时间以及个人水平,没有对锁的内容进行深层次的讲解。
只是对常用的锁以及常见的锁的概念进行了基本介绍。
有时间从源码以及实际应用的角度进行了对比分析。
开篇所提到的面试题文章来自简书~




