【Java实习生面试题系列】-- 多线程篇二
当前对象实例
的锁synchronized
关键字加到 static
静态方法和 synchronized(class)
代码块上都是是给 Class
类上锁给定对象
加锁,进入同步代码库前要获得给定对象的锁特别注意:
①如果一个线程A调用一个实例对象的非静态 synchronized
方法,而线程B需要调用这个实例对象所属类的静态 synchronized
方法,是允许的,不会发生互斥现象,因为访问静态 synchronized
方法占用的锁是当前类的锁
②尽量不要使用 synchronized(String s)
,因为JVM中,字符串常量池具有缓冲功能
unlock
操作先行发生 (happen-before)
于后面对同一个锁的 lock
操作”。synchronized
同步代码块的实现是通过 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。当执行 monitorenter
指令时,线程试图获取锁也就是获取 monitor
(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。
其内部包含一个计数器,当计数器为 0
则可以成功获取,获取后将锁计数器设为 1
也就是加1。相应的在执行 monitorexit
指令后,将锁计数器设为 0
,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止
synchronized
修饰的 方法
并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
synchronized
锁升级原理:在锁对象的对象头里面有一个 threadid
字段,在第一次访问的时候 threadid
为空,jvm 让其持有偏向锁,并将 threadid
设置为其线程 id
,再次进入的时候会先判断 threadid
是否与其线程 id
一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁
,此过程就构成了 synchronized
锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized
的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
synchronized
的非公平其实在源码中应该有不少地方,因为设计者就没按公平锁来设计,核心有以下几个点:
1)当持有锁的线程释放锁时,该线程会执行以下两个重要操作:
在1和2之间,如果有其他线程刚好在尝试获取锁(例如自旋),则可以马上获取到锁。
2)当线程尝试获取锁失败,进入阻塞时,放入链表的顺序,和最终被唤醒的顺序是不一致的,也就是说你先进入链表,不代表你就会先被唤醒。
从最近几个jdk版本中可以看出,Java的开发团队一直在对synchronized优化,其中最大的一次优化就是在jdk6的时候,新增了两个锁状态,通过 锁消除、锁粗化、自旋锁
等方法使用各种场景,给 synchronized
性能带来了很大的提升。
上面讲到锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
一句话总结它的作用:减少统一线程获取锁的代价。在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得,那么此时就是偏向锁。
核心思想:
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word
的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word
的锁标记位为偏向锁以及当前线程ID等于Mark Word
的ThreadID即可,这样就省去了大量有关锁申请的操作。
轻量级锁是由偏向锁升级而来,当存在 第二个线程申请同一个锁对象
时,偏向锁就会立即升级为轻量级锁。注意这里的第二个线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。
重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。
重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。
消除锁是虚拟机另外一种锁的优化,这种优化更彻底,在JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁。比如下面代码的method1
和method2
的执行效率是一样的,因为object
锁是私有变量,不存在所得竞争关系。
锁粗化是虚拟机对另一种极端情况的优化处理,通过扩大锁的范围,避免反复加锁和释放锁
。比如下面 method3
经过锁粗化优化之后就和 method4
执行效率一样了。
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起
,还会进行一项称为自旋锁的优化手段。
它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
,这就解决了自旋锁带来的缺点。重量级锁底层依赖于 系统的同步函数
来实现,在 linux 中使用 pthread_mutex_t
(互斥锁)来实现。
这些底层的同步函数操作会涉及到:操作系统用户态和内核态的切换、进程的上下文切换,而这些操作都是比较耗时的,因此重量级锁操作的开销比较大。
而在很多情况下,可能获取锁时只有一个线程,或者是多个线程交替获取锁
,在这种情况下,使用重量级锁就不划算了,因此引入了偏向锁和轻量级锁来降低没有并发竞争时的锁开销。
1.两者都是可重入锁
可重入锁指的是在一个线程中可以多次获取同一把锁
,比如: 一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁, 两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized
是依赖于 JVM
实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized
关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的ReentrantLock
是 JDK
层面实现的(也就是 API 层面,需要 lock()
和 unlock()
方法配合 try/finally
语句块来完成)3.ReentrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)
lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock
类的 ReentrantLock(boolean fair)
构造方法来制定是否是公平的。4.使用选择
ReentrantLock
的高级功能,否则优先使用 synchronized
。ReentrantLock
不是所有的 JDK
版本都支持,且它需要显式的释放锁。并且使用 synchronized
不用担心没有释放锁而导致死锁问题,因为 JVM
会确保锁的释放volatile
解决的是内存可见性问题,会使得所有对 volatile
变量的读写都直接写入主存,即 保证了变量的可见性。
synchronized
解决的事执行控制的问题,它会阻止其他线程获取当前对象的监控锁,这样一来就让当前对象中被 synchronized
关键字保护的代码块无法被其他线程访问,也就是无法并发执行。而且,synchronized
还会创建一个 内存屏障,内存屏障指令保证了所有 CPU 操作结果都会直接刷到主存中,从而 保证操作的内存可见性,同时也使得这个锁的线程的所有操作都 happens-before
于随后获得这个锁的线程的操作。
两者的区别主要有如下:
volatile的两层语义:
volatile
保证变量对所有线程的可见性:当 volatile
变量被修改,新值对所有线程会立即更新。或者理解为多线程环境下使用volatile
修饰的变量的值一定是最新的 。volatile
完全避免了指令重排优化,实现了有序性。volatile的原理:
volatile
多加了 lock addl
指令,这个操作相当于一个内存屏障,使得 lock
指令后的指令不能重排序到内存屏障前的位置。这也是为什么JDK1.5以后可以使用双锁检测实现单例模式。(内存屏障实现避免指令重排
)lock
前缀的另一层意义是使得本线程工作内存中的 volatile
变量值立即写入到主内存中,并且使得其他线程共享的该volatile
变量无效化,这样其他线程必须重新从主内存中读取变量值 。这一篇的面试题是面试重难点,所以我就不放太多题上来了,好好吃透。总结面试题也花费了我不少时间,所以说总结不易,如果你感觉对你有帮助的话,请你三连支持,后面的文章会一点点更新。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_56727438/article/details/124451624
内容来源于网络,如有侵权,请联系作者删除!