多线程锁相关面试

本文探讨了Java并发编程中的SynchronizedMap和ConcurrentHashMap的区别,包括它们在多线程环境下的性能优化。同时,讲解了乐观锁和悲观锁的概念,以及如何在Java中实现它们。此外,还介绍了Lock接口和可重入锁的特点,以及如何使用JavaAPI中的工具如wait/notify和ConcurrentHashMap来提高并发性能。

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

1、SynchronizedMap和ConcurrentHashMap有什么区别?

SyncronizedMap一次锁住整张表来保证线程安全,所以每次只能有一个线程来访问map。

ConcurrentHashMap使用分段锁来保证在多线程下的性能。ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

另外ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,interator完成后再将头指针替换为新的数据。

2、乐观锁和悲观锁的理解及如何实现?有哪些实现方式?

乐观锁和悲观锁的理解及如何实现?有哪些实现方式?

悲观锁:总是假设最坏的情况,每次去拿数据的时候总是认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

传统的关系型数据库里面就用到了很多这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语syncronized关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在java 中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的实现方式:

1、使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。

2、Java中的Compare and Swap即CAS,当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程能更新变量的值,而其他线程都失败,失败的线程并不会挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS操作中包含三个操作数 ,需要读写的内容位置、进行比较的预期原值和拟写入的新值。如果内存位置的值 与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则处理器不做任何操作。

3、当一个线程进入某个对象的一个synchronized的实例方法后,其他线程是否可进入此对象的其他方法?

如果其他方法没有synchronized的话,其他线程是可以进入的。所以要开放一个线程安全的对象时,得保证每个方法都是线程安全的。

4、什么是可重入锁?

outer中调用了inner,outer先锁住了lock,这样inner就不会再获取lock。其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源,这种锁即称为不可重入就意味着,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

synchronized、ReentrantLock都是可重入锁,可重入锁相对来说简单化了并发编程的开发。

5、Java Concurrency API中的lock接口是什么?

Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

可以使锁更公平;可以是线程在等待锁的时候响应中断;可以让线程尝试获取锁,并在无法获取锁的时候理解返回或者等待一段时间;可以再不同的范围,以不同的顺序获取和释放锁。

整体来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock待参数方法)、可中断的(lockInterrupibly)、可多条件队列的(newCondition方法)锁操作。另外lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

6、Java中怎么唤醒一个阻塞的线程?

在Java发展史上曾经使用suspend()、resume()方法对于线程阻塞进行唤醒,但是随着出现很多问题,比较典型的还是死锁问题。解决方案可以使用以对象为目标的阻塞,即利用Object类的wait()和notify()方法实现线程阻塞。

首先,wait、notify方法是针对对象的,调用任意对象的wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才往下执行;

其次,wait、notify方法必须在synchronized块或方法中被调用,并且要保证同步块或方法的锁对象与调用wait、notify 方法的对象是同一个,如此一来在调用wait阻塞后当前线程就将之前获取的对象锁释放。

7、SynchronizedMap和ConcurrentHashMap有什么区别?

SyncronizedMap一次锁住整张表来保证线程安全,所以每次只能有一个线程来访问map。

ConcurrentHashMap使用分段锁来保证在多线程下的性能。ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

另外ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,interator完成后再将头指针替换为新的数据。

8、什么是并发容器的实现?

何为同步容器:可以简单理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如Voctor,Hashtable,以及Collectons.synchronizedSet,synchronizedList等方法返回的容器。可以通过查看Vector,hashtable等这些同步容器的上线代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchronized。

并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种颗粒度更细的加锁机制,可以成为分段锁,在这种锁机制下,允许任意数量的读取线程并发访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以再并发环境下实现更高的吞吐量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵然间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值