当面试官问:有了解过几种锁?
青铜:悲观锁,乐观锁,公平锁,emmmmmm还有很多种其他的锁,但是记不起来了。
如果是上面这种回答的话,场面还是有点尴尬的,下面给大家一个王者版的回答。
王者:锁可以分为五种,第一种:悲观锁和乐观锁;第二种:公平锁和非公平锁;第三种:共享锁和独享锁;第四种:可重入锁和不可重入锁;第五种:自旋锁、分段锁、死锁。
下面将对以上五种锁进行详细解释:
种类一
- 悲观锁
当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized、ReentrantLock
- 乐观锁
每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断
如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步
比如说数据库的乐观锁,通过版本控制来实现,乐观的认为在数据更新期间没有其他线程影响
- 小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多
种类二
- 公平锁
指多个线程按照申请锁的顺序来获取锁,简单来说如果一个线程组里,能保证每个线程都能拿到锁
比如Reentrantlock(底层是同步队列FIFO:First Input First Output来实现)
- 非公平锁
获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到铁
比如synchronized、ReentrantLock
种类三
- 独享锁 (互斥锁)
也叫排它锁/写锁/独占锁/独享锁/该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞
直到当前线程解锁。例子:如果 线程A对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁
获得互斥锁的线程即能读数据又能修改数据
- 共享锁
也叫S锁/读锁,能查看但无法修改和删除的一种数据锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享
种类四
- 可重入锁
也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
- 不可重入锁
若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
- 小结:可重入锁能一定程度的避免死锁,synchronized、ReentrantLock 重入锁
种类五
- 自旋锁
一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待
然后不断的判断锁是否能够被成功获取广直到获取到锁才会退出循环, 任何时刻最多只能有一个执行单元获得锁
小结:不会发生线程状态的切换:一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU
- 分段锁
并不是具体的一种锁,只是一种锁的设计,将数据分段上锁,把锁进一步细粒度化,可以提升并发量
当操作不需要更新整个数组的时候,就仅针对数组中的一项进行加锁操作
CurrentHashMap底层就用了分段锁
- 死锁
两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去
问:synchronized和ReentrantLock是什么类型的锁?
-
synchronized
- synchronized是解决线程安全的问题,常用在同步普通方法、静态方法、代码块中
- 每个对象有一个锁和一个等待队列,锁只能被一个线程持有,其他需要锁的线程需要阻塞等待
- 锁被释放后,对象会从队列中取出一个并唤醒,唤醒哪个线程是不确定的,不保证公平性
- 所以synchronized是非公平、可重入的悲观锁
- ReentrantLock
ReentrantLock是可重入的悲观锁,它的公平与否取决于参数控制。默认是非公平的。
o ReentrantLock里面有一个内部类Sync, Sync继承AQS AbstractQueuedSynchronizer
o 有公平锁FairSync和非公平锁NonfairSync两个子类,ReentrantLock默认使用非公平锁
o 公平锁与非公平锁的lock()方法唯一的区别是多了一个限制条件:hasQueuedPredecessors()
1、面试官:非公平锁会带来【锁饥饿】,说下什么是【锁饥饿】?
- 当有几个线程同时来抢占锁时,其中有的线程一直抢到锁
- 但一些线程由于优先级太低,一直得不到 CPU 调度执行,导致其他线程一直抢不到锁,这个就是出现了锁饥饿。
2、面试官:ReentrantLock为啥要设计有公平锁和非公平锁,他们有什么优缺点?
- 公平锁获取锁和释放锁后的相关操作,相关线程也会从休眠和恢复之间变化,这个就涉及到用户态内核态互相转变
- 非公平锁获取锁不用遵循先到先得的规则,没有了阻塞和恢复执行的步骤,避免了线程休眠和恢复的操作
- 所以非公平锁性能高于公平锁,更能重复利用CPU的时间
- 如果是为了各个线程都可以获取锁资源,则推荐采用公平锁,等待锁的线程不会饿死
- 如果是为了业务有更大的吞吐量,则推荐采用非公平锁
3、结论
。 在Java 语言中,锁的默认实现都是非公平锁,原因是非公平锁的效率更高
。 非公平锁注重的是性能,而公平锁注重的是锁资源的平均分配
。 各有优缺点,业务需要根据实际情况决定