并发(4)

目录

16.sychronized修饰方法在抛出异常时,会释放锁吗?

17.多个线程等待同一个sychronized锁的时候,JVM如何选择下一个获取锁的线程?

18.sychronized是公平锁吗?

19.volatile关键字的作用是什么?

20.volatile能保证原子性吗?

21.volatile是如何实现可见性的?

22.volatile是如何实现有序性的?

23.说下volatile的应用场景?

24.所有的final修饰的字段都是编译期常量吗?

25.如何理解private所修饰的方法是隐式地final?


16.sychronized修饰方法在抛出异常时,会释放锁吗?

17.多个线程等待同一个sychronized锁的时候,JVM如何选择下一个获取锁的线程?

非公平锁,即抢占式。

18.sychronized是公平锁吗?

synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能导致饥饿现象。

19.volatile关键字的作用是什么?

防重排序

我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。

其源码如下:

并发(4)_第1张图片

现在我们分析以下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分三个步骤:

1.分配内存空间。

2.初始化对象。

3.将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

1.分配内存空间。

2.将内存空间的地址赋值给对应的引用。

3,初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

实现可见性

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓冲区-线程工作内存。

volatile关键字能有效的解决这个问题,我们看下下面的例子,就可以直到其作用:

并发(4)_第2张图片

直观上说,这段代码的结果只可能有两种:b=3;a=3或者b=2;a=1。不过运行上面的代码(可能时间上要长一些),你会发现除了上面两种结果之外,还出现了另外两种结果:

并发(4)_第3张图片

为什么会出现b=2;a=3和b=3;a=1这种结果呢?正常情况下,如果先执行change方法,再执行print方法,输出结果应该为b=3;a=3。相反,如果先执行的print方法,再执行change方法,结果应该是b=2;a=1。那b=3;a=1的结果是怎么出来的呢?原因就是第一个线程将值a=3修改后,但是对第二个线程是不可见的,所以才出现这一结果。如果将a和b都改成volatile类型的变量再执行,则再也不会出现b=2;a=3;a=1的结果了。

保证原子性:单次读/写

volatile不能保证完全的原子性,只能保证单次读/写操作具有原子性。

20.volatile能保证原子性吗?

不能完全保证,只能保证单次的读/写操作具有原子性。

21.volatile是如何实现可见性的?

内存屏障。

22.volatile是如何实现有序性的?

happens-before等

23.说下volatile的应用场景?

使用volatile必须具备的条件:

1.对变量的写操作不依赖于当前值

2.该变量没有包含在具体其他变量的不变式中。

3.只有在状态真正独立于程序内其他内容时才能使用volatile。

例子1:单例模式

单例模式的一种实现方式,但很多人会忽略volatile关键字,因为没有该关键字,程序也可以很好的运行,只不过代码的稳定性综合不是100%,说不定在未来的某个时刻,隐藏的Bug就出来了。

并发(4)_第4张图片

例子2:volatile bean

在volatile bean模式中,JavaBean的所有数据成员都是volatile类型的,并且getter和setter方法必须非常普通-除了获取或者设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组的属性,因为当数组引用被声明为volatile时,只有引用而不是数组本身具有volatile语义)。对于任何volatile变量,不变式或约束都不能包含JavaBean属性。

并发(4)_第5张图片

24.所有的final修饰的字段都是编译期常量吗?

不是

25.如何理解private所修饰的方法是隐式地final?

类中所有private方法都隐式地指定为final的,由于无法取用private方法,所以也就不能覆盖他。可以对private方法增添final关键字,但这样做并没有什么好处。

看下下面的例子:

并发(4)_第6张图片

Base和Son都有方法test(),但是这并不是一种覆盖,因为private所修饰的方法是隐式地final,也及时无法被继承,所以更不用说是覆盖了,在Son中test()方法不过是属于Son的新成员罢了,Son进行向上转型得到father,但是father.test()是不可执行的,因为Base中的test()方法是private的,无法被访问到。

你可能感兴趣的:(并发,面试题,并发)