面试6-锁与并发

juc是Java1.5之后才有的

1.Volatile关键字你了解吗,说说你的理解?

答:第一,关键字volatile是Java虚拟机提供的最轻量级的同步机制。当一个变量被定义成volatile之后, 具备两种特性:

①保证此变量对所有线程的可见性。当一条线程修改了这个变量的值,新值对于其他线程是 可以立即得知的。而普通变量做不到这一点。

②禁止指令重排序优化。普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不 保证程序代码的执行顺序。

第二,为什么基于volatile变量的运算在并发下不一定是安全的?

因为volatile变量在各个线程的工作内存,不存在一致性问题(各个线程的工作内存中volatile变 量,每次使用前都要刷新到主内存)。但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。

第三,为什么使用volatile?

因为在某些情况下,volatile同步机制的性能要优于锁(synchronized关键字),但是由于虚拟机 对锁实行的许多消除和优化,所以并不是很快。

volatile变量读操作的性能消耗与普通变量几乎没有差别,但是写操作则可能慢一些,因为它 需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

2.你对CAS算法了解吗,说说你的理解?

答:CAS算法是Compare And Swap即比较然后替换,整个操纵是保证原子性的,假设读取值是0,预估值也是0,然后才会把要更新的值进行替换,如果读取值和预估值不一样,将不会进行任何操作。

       juc提供的原子变量就是使用了CAS算法保证变量的原子性的。

3.什么是线程死锁?

答:死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

4.死锁产生的条件是什么?

答:①互斥条件:该资源任意一个时刻只由一个线程占用

②请求与保持条件:一个线程 / 进程因请求资源而阻塞时,对已获得的资源保持不放

③不剥夺条件:线程 / 进程已获得的资源在末使用完之前不能被其他线程 /进程强行剥夺,只有自己使用完毕后才释放资源

④循环等待条件:若干线程 / 进程之间形成一种头尾相接的循环等待资源关系

5.如何避免线程死锁?

答:针对死锁产生的条件进行一一拆解:

①破坏互斥条件:无法破坏,因为使用锁的本意就是想让它们互斥的(临界资源需要互斥访问)

②破坏请求与保持条件:一次性申请所有的资源

③破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源

④破坏循环等待条件:按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件(最常用)

6.说说 synchronized 和 java.util.concurrent.locks.Lock 的异同?

答:相同点:Lock 能完成 synchronized 所实现的所有功能。

不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 则要求手工释放。

更具体地来说,有以下差异:

①含义不同,Synchronized 是关键字,属于 JVM 层面,底层是通过 monitorenter 和monitorexit 完成,依赖于 monitor 对象来完成;

Lock 是 java.util.concurrent.locks.lock 包下的,是 JDK1.5 以后引入的新API 层面的锁

②使用方法不同:Synchronized 不需要用户手动释放锁,代码完成之后系统自动让线程释放锁;

ReentrantLock 需要用户手动释放锁,没有手动释放可能导致死锁。

③等待是否可以中断:Synchronized 不可中断,除非抛出异常或者正常运行完成;ReentrantLock 可以中断。一种是通过 tryLock (long timeout, TimeUnit unit) ,另一种是lockInterruptibly () 放代码块中,调用 interrupt () 方法进行中断

④加锁是否公平:Synchronized 是非公平锁;ReentrantLock 默认非公平锁,可以在构造方法传入 boolean 值,true 代表公平锁,false 代表非公平锁;

7. AtomicInteger 怎么实现原子操作的?AtomicInteger 有哪些缺点?

答:AtomicInteger 内部使用 CAS 原子语义来处理加减等操作,CAS 全称Compare And Swap(比较与交换),通过判断内存某个位置的值是否与预期值相等,如果相等则进行值更新。

CAS 是内部是通过 Unsafe 类实现,而 Unsafe类的方法都是 native 的,在 JNI 里是借助于一个 CPU 指令完成的,属于原子操作。

AtmoicInteger 缺点有:①循环开销大。如果 CAS 失败,会一直尝试。如果 CAS 长时间不成功,会给 CPU 带来很大的开销;

②只能保证单个共享变量的原子操作,对于多个共享变量,CAS 无法保证;

③存在 ABA 问题:就 AtomicInteger 而言,CAS 需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是 A,后来变成了 B,然后又变成了 A,那么 CAS 进行检查时会发现值

没有发生变化,但是实际上是有变化的。解决方案:ABA 问题一般通过版本号的机制来解决。每次变量更新的时候,版本号加 1,这样只要变量被某一个线程修改过,该版本号就会发生递增操作。

ABA场景下的变化过程就从 “A-B-A” 变成了 “1A-2B-3A”。JDK 从 1.5 开始提供了 AtomicStampedReference 类来解决 ABA 问题,具体操作封装在 compareAndSet () 中。compareAndSet ()

首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。

8.说说并发与并行的区别?

答:并发(conurrent): 在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。类似操作系统的时间片分时调度。

打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。

并行(Parallel):当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

所以,并发是指在一段时间内宏观上多个程序同时运行。并行指的是同一个时刻,多个任务确实真的在同时运行。并发,指的是多个事情,在同一时间段内同时发生了。

并行,指的是多个事情,在同一时间点上同时发生了。并发的多个任务之间是互相抢占资源的。并行的多个任务之间是不互相抢占资源的、只有在多CPU的情况中,才会发生并行。

否则,看似同时发生的事情,其实都是并发执行的。

这里面有一个很重要的点,那就是系统要有多个CPU才会出现并行。在有多个CPU的情况下,才会出现真正意义上的『同时进行』。

9.说说 sleep() 方法和 wait() 方法区别和共同点?

答:两者最主要的区别在于:sleep()方法没有释放锁,而 wait()方法释放了锁。

两者都可以暂停线程的执行;wait()通常被用于线程间交互/通信,sleep()通常被用于暂停执行;

wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒。

10.说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗?

答:JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

①偏向锁:无竞争条件下,消除整个同步互斥,连CAS都不操作。消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。即在无竞争的情况下,把 整个同步都消除掉。这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁 没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。

②轻量级锁:无竞争条件下,通过CAS消除同步互斥,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

③自旋锁:为了减少线程状态改变带来的消耗,不停地执行当前线程;

④自适应自旋锁:自旋的时间不固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 如果一个锁对象,自旋等待刚刚成功获得锁,并且持有锁的线程正在运行,那么虚拟机认 为这次自旋仍然可能成功,进而运行自旋等待更长的时间。 如果对于某个锁,自旋很少成功,那在以后要获取这个锁,可能省略掉自旋过程,以免浪费处理器资源。 有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会 越来越准确,虚拟机也会越来越聪明。

⑤锁消除:不可能存在共享数据竞争的锁进行消除;

⑥锁粗化:将连续的加锁,精简到只加一次锁。原则上,同步块的作用范围要尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和 解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。 锁粗化就是增大锁的作用域;

其中,锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

11.自旋锁解决什么问题?自旋锁的原理是什么?自旋的缺点?

答:(1)自旋锁解决什么问题?

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完 成,这些操作给系统的并发性带来很大压力。同时很多应用共享数据的锁定状态,只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。先不挂起线程,等一会儿。

(2)自旋锁的原理是什么?

如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,让后面请求锁的线程稍等一会,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放。为了让线程等待,我们只需让线程执行一个忙循环(自旋)。

(3)自旋的缺点?

自旋等待本身虽然避免了线程切换的开销,但它要占用处理器时间。所以如果锁被占用的时间 很短,自旋等待的效果就非常好;如果时间很长,那么自旋的线程只会白白消耗处理器的资 源。所以自旋等待的时间要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁, 那就应该使用传统的方式挂起线程了。

12.单机场景下常用的并发控制手段有哪些?

答:最基础的:1.同步方法synchronized;2.同步块synchronized。

进阶的:重入锁ReentrantLock,Semaphore信号量

13.什么是公平锁?什么是非公平锁?

答:公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

14Java主流锁的分类有哪些?

答:第一,根据线程要不要锁住同步资源分类:锁住(悲观锁),不锁柱(乐观锁);

第二,锁住同步资源失败,线程要不要阻塞分类:阻塞(),不阻塞(自旋锁,适应性自旋锁);

第三,多个线程竞争同步资源的流程细节有没有区别分类:无锁(不锁柱资源,多个 线程只有一个能修改资源成功,其他线程会重试)、偏向锁(同一个线程执行同步资源时自动获取资源)、

       轻量级锁(多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放)、重量级锁(多个 线程竞争同步资源,没有获取资源的线程阻塞等待唤醒);

第四,多个线程竞争锁时要不要排队分类:排队(公平锁)、先尝试插队,插队失败再排队(非公平锁);

第五,一个线程中的多个流程能不能获取同一把锁分类:能(可重入锁)、不能(非可重入锁);

第六,多个线程能不能共享一把锁分类:能(共享锁)、不能(排他锁);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值