【大厂Java并发编程面试题解】显式锁(Explicit Locks)(1)

本文详细探讨了Java并发编程中的ReentrantLock、BoundedBuffer实现以及公平性和非公平锁的区别,强调了性能、公平性对并发应用的影响,并提到了读写锁的选择。此外,还涉及了死锁检测和内置锁的使用策略。
摘要由CSDN通过智能技术生成

}

DollarAmount(int dollars) {

}

}

class Account {

public Lock lock;

void debit(DollarAmount d) {

}

void credit(DollarAmount d) {

}

DollarAmount getBalance() {

return null;

}

}

class InsufficientFundsException extends Exception {

}

}

3.2 带有时间限制的锁


3.3 可中断的锁


3.4关于Condition


最典型的就是阻塞的有界队列的实现。

public class BoundedBuffer {

private static final Logger logger = LoggerFactory.getLogger(BoundedBuffer.class);

final Lock lock = new ReentrantLock();

final Condition notFull = lock.newCondition();

final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[2]; // 阻塞队列

int putptr, takeptr, count;

private void log(String info) {

logger.info(Thread.currentThread().getName() + " - " + info);

}

public void put(Object x) throws InterruptedException {

log(x + “,执行put”);

lock.lock();

log(x + “,put lock.lock()”);

try {

while (count == items.length) { // 如果队列满了,notFull就一直等待

log(x + “,put notFull.await() 队列满了”);

notFull.await(); // 调用await的意思取反,及not notFull -> Full

}

items[putptr] = x; // 终于可以插入队列

if (++putptr == items.length) {

putptr = 0; // 如果下标到达数组边界,循环下标置为0

}

++count;

log(x + “,put成功 notEmpty.signal() 周知队列不为空了”);

notEmpty.signal(); // 唤醒notEmpty

} finally {

log(x + “,put lock.unlock()”);

lock.unlock();

}

}

public Object take() throws InterruptedException {

log(“执行take”);

lock.lock();

Object x = null;

log(“take lock.lock()”);

try {

while (count == 0) {

log(“take notEmpty.await() 队列为空等等”);

notEmpty.await();

}

x = items[takeptr];

if (++takeptr == items.length) {

takeptr = 0;

}

–count;

log(x + “,take成功 notFull.signal() 周知队列有剩余空间了”);

notFull.signal();

return x;

} finally {

lock.unlock();

log(x + “,take lock.unlock()”);

}

}

public static void main(String[] args) throws InterruptedException {

final BoundedBuffer bb = new BoundedBuffer();

ExecutorService executor = Executors.newFixedThreadPool(10);

for (char i = ‘A’; i < ‘F’; i++) {

final char t = i;

executor.execute(() -> {

try {

bb.put(t);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

List res = new LinkedList<>();

for (char i = ‘A’; i < ‘F’; i++) {

executor.execute(() -> {

try {

char c = (char) bb.take();

res.add©;

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

try {

executor.awaitTermination(2, TimeUnit.SECONDS);

} catch (InterruptedException ie) {

ie.printStackTrace();

}

logger.info(res.toString());

executor.shutdownNow();

}

}

4 性能考虑因素

=======================================================================

Java5的时候J.U.C的ReentrantLock锁竞争性能非常好,到了Java6使用了改进后的算法来管理内置锁,所以现在差不太多了,只好一点点

竞争性能的影响可伸缩性的关键要素:如果有越多的资源被耗费在锁的管理和线程调度上,那么应用程序得到的资源就越少,锁的实现方式越好,将需要越少的系统调用和上下文切换。

5 公平性

====================================================================

ReentrantLock默认创建非公平的锁,非公平指被阻塞挂起的线程(LockSupport.park)都在AQS的CLH队列中排队等待自己被唤醒。他们是按照发出的请求顺序来排队的,但一旦有一个唤醒的就会和新来的线程竞争锁,新来的可能会“插队”。若新来的成功获取锁,那么它将跳过所有等待线程而开始执行,这意味着本该被唤醒的线程失败了,对不起您回到队列的尾部继续等。

一般,非公平锁的性能要好于公平锁。

因为一个线程被唤醒是需要时间的,挂起线程和唤醒恢复线程都存在开销,这个空隙如果有其他线程处于ready状态,无需上下文切换,那么直接运行就行。

A持有锁,B请求,但B在恢复的过程中,C可以插队"非公平"的获取锁,然后执行再释放,这时候B刚刚好做完上下文切换可以执行,这个对于B和C来说是一个“双赢”的局面,是提高吞吐量的原因。

JVM也没有在其内置锁上采用公平性的机制。

6 选型

===================================================================

除非使用到3提到的高级特性,或者内置锁无法满足需求时,否则还是老实用内置锁,毕竟是JVM自身提供的,而不是靠类库,因此可能会执行一些优化。

另外内置锁在利用kill -3 dump thread的时候可以发现栈帧上的一些monitor lock的信息,识别死锁,而J.U.C的锁这方面就不太行,当然JAVA6之后提供了管理和调试接口解决了。

7 读-写锁

=====================================================================

ReentrantLock每次只有一个线程能持有锁,但是这种严格的互斥也会抑制并发。会抑制

  • 写/写

  • 写/读

  • 读/读

冲突,但是很多情况下读操作是非常多的,如果放宽加锁的需求,允许多个读操作可以同时访问数据,那么就可以提升性能。

但是要保证读取的数据是最新的,不会有其他线程修改数据

使用ReadWriteLock的场景:

  • 一个资源可以被多个读操作访问

  • 被一个写操作访问

  • 但二者不能同时进行

如果读线程正在持有锁,这时候另外一个写线程,那么会优先获取写锁:

public class ReadWriteMap<K, V> {

private final Map<K, V> map;

private final ReadWriteLock lock=new ReentrantReadWriteLock();

private final Lock r=lock.readLock();

private final Lock w=lock.writeLock();

public ReadWriteMap(Map<K, V> map) {

this.map=map;

}

public V put(K key, V value) {

w.lock();

try {

return map.put( key, value );

} finally {

w.unlock();

}

}

public V remove(Object key) {

w.lock();

try {

return map.remove( key );

} finally {

w.unlock();

}

}

public void putAll(Map<? extends K, ? extends V> m) {

w.lock();

try {

map.putAll( m );

} finally {

w.unlock();

}

}

public void clear() {

w.lock();

try {

map.clear();

} finally {

w.unlock();

}

}

public V get(Object key) {

r.lock();

try {

return map.get( key );

} finally {

r.unlock();

}

}

public int size() {

r.lock();

try {

return map.size();

} finally {

r.unlock();

}

}

public boolean isEmpty() {

r.lock();

try {

return map.isEmpty();

} finally {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例

MyBatis答案解析
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例
[外链图片转存中…(img-UF2sNFYV-1713411103948)]

[外链图片转存中…(img-0ng1h2kF-1713411103949)]
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值