java面试_锁

本文详细介绍了Java中的锁机制,包括锁的概念、锁的开销、锁竞争和死锁,以及如何避免死锁。重点讲解了死锁产生的原因和解决策略,如加锁顺序、加锁时限和死锁检测。同时,文中还提到了不同类型的锁,如悲观锁、乐观锁、独享锁、共享锁、公平锁和非公平锁,并探讨了可重入锁和自旋锁的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 锁

什么是锁?
在计算机科学中,锁(lock)或互斥(mytex)是一种同步机制,用于在许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他,开发控制策略

锁的概念
1.锁开销:指的是锁占用内存空间,cpu初始化和销毁锁,获取和释放锁的时间。程序使用的锁越多,响应的锁开销越大
2.锁竞争:是指一个进程或线程试图获取另一个进程或线程持有的锁,就会发生锁竞争,锁粒度越小,发生锁竞争的可能性就越小。
3.死锁: 至少两个任务中的每一个都等待另一个任务持有的锁的情况锁粒度是衡量锁保护的数据量大小,通常选择粗粒度的锁(锁的数量少,每个锁保护大量的数据),在当单进程访问受保护的数据时锁开销小,但是当多个进程同时访问时性能很差。因为增大了锁的竞争。相反,使用细粒度的锁(锁数量多,每个锁保护少量的数据)增加了锁的开销但是减少了锁竞争。例如数据库中,锁的粒度有表锁、页锁、行锁、字段锁、字段的一部分锁

死锁的产生以及如何避免
Java中死锁最简单的情况是:一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,就是说线程T1在等在线程T2释放锁L2,而线程T2在等在线程T1释放锁L1,这这样就导致T1和T2陷入永久的死循环,导致了死锁。
== 造成死锁的原因有以下四条:==
1.互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2.不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
3.请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4.存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有,如图1所示。
在这里插入图片描述
如何避免死锁:

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(为线程尝试获取锁加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  • 死锁检测

总结:避免死锁的方式
1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实。
2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。
3、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后便会返回一个失败信息。
我们可以使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间。这样就打破了死锁的闭环。比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1。此时如果T3申请锁L1失败,那么T3释放锁L3,并进行睡眠,那么T2就可以获得L3了,然后T2执行完之后释放L2, L3,所以T1也可以获得L2了执行完然后释放锁L1, L2,然后T3睡眠醒来,也可以获得L1, L3了。打破了死锁的闭环。

锁的类型
首先,先上整体图。
在这里插入图片描述

悲伤锁与乐观锁
悲伤锁:悲伤锁认为自己在使用数据的时候一定有别的线程修改数据,在获取数据的时候会先加锁,确保数据不会被别的数据修改。
锁实现:关键字synchronized ,接口Lock的实现类。
适用场景:写操作较多,先加锁可以保证写数据操作正确
乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只会在更新数据的时候去判断之前有没有别的数据更新了这个数据。
锁实现:CAS算法,例如AtomicInteger类的原子自增是通过cas自旋实现
适用场景:读操作较多,不加锁的特点就是能够使其读操作的性能大幅提升

下面是悲观锁和乐观锁的执行过程:
在这里插入图片描述
在这里插入图片描述

独享锁与共享锁
独享锁,就是说该锁一次只能被一个线程占有。
共享锁是指该锁可以被多个线程持有。

公平锁和非公平锁
公平锁是指多个申请锁的线程在队列中按照顺序来获取锁
非公平锁是指多个申请锁的线程在队列中申请锁的顺序是不一致的,有可能后申请的先获得锁。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的控制线程对锁的获取, 所以并没有任何办法使其变成公平锁

可重入锁
可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

自旋锁
自旋锁是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁。好处是减少上下文切换,缺点是一直占用CPU资源。下面是图示:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值