欢迎来到“多核时代”!多线程,并发锁?今天就来带你解析看看什么是“多线程、并发锁”

多线程编程,是 “多核时代” 提升计算性能的关键因素之一,亦属于开发者必须掌握的核心技能。

欢迎来到“多核时代”!多线程,并发锁?今天就来带你解析看看什么是“多线程、并发锁”

现在不管是大公司还是小公司,去面试都会问到多线程与并发编程的知识,大家面试的时候这方面的知识一定要提前做好储备。

JVM 对 Java 的 原 生 锁 做 了 哪 些 优 化 ?

在Java6之 前 ,Monitor的实现完全依赖底层操作系统的互斥锁来实现,也就是我们刚才在问题二中所阐述的获取/释放锁的逻辑 。

由于Java层面的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代JDK中做了大量的优化。

一种优化是使用自旋锁,即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作,避免了用户态到内核态的切换。

现代JDK中还提供了三种不同的Monitor实现,也就是三种不同的
锁 :

  • 偏 向 锁 ( Biased Locking)
  • 轻 量 级 锁
  • 重 量 级 锁

这三种锁使得JDK得以优化Synchronized的运行,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这就是锁的升级、降级。

  • 当没有竞争出现时,默认会使用偏向锁 。

JVM会利用CAS操作,在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁 , 因为在 很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

  • 如果有另一线程试图锁定某个被偏斜过的对象,JVM就撤销偏斜锁,切换到轻量级锁实现。

  • 轻量级锁依赖CAS操作Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

什 么 是 锁 消 除 和 锁 粗 化 ?

  • 锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。
    主要根据逃逸分析。

程序员怎么会在明知道不存在数据竞争的情况下使用同步呢?很多不是程序员自己加入的。

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

锁粗化就是增大锁的作用域。

乐 观 锁 一 定 就 是 好 的 吗 ?

乐观锁避免了悲观锁独占对象的现象

同时也提高了并发性能,但它也有缺点:

  • 乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及 对象颗粒度大小。

  • 长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。

  • ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断 内 存 值 是 否 被 改 过 , 但 这 个 判 断 逻 辑 不 严 谨 , 假 如 内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种 情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。

你 刚 才 提 到 获 取 对 象 的 锁 , 这 个 “ 锁 ” 到 底 是 什 么 ? 如 何 确 定 对 象 的 锁 ?

“ 锁 ”的本质其实是 monitorenter和 monitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。我们知道,使用Synchronized可以修饰不同的对象,因此,对应的对象锁可以这么确定。

  • 如果Synchronized明确指定了锁对象,比如Synchronized( 变量名 )、Synchronized( this )等,说明加解锁对象为该对象。
  • 如 果 没 有 明 确 指 定 :
    若 Synchronized 修饰的方法为非静态方法,表示此方法对应的对象为锁对象;
    若 Synchronized 修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。

注意,当一个对象被锁住时,对象里面所有用 Synchronized 修饰的方法都将产生堵塞,而对象里非 Synchronized 修饰的方法可正常被调 用,不受锁影响。

什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。比如下面的伪代码,一个类中的同步方法调用另一个同步方法,假如Synchronized不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。

对 Synchronized 来说,可重入性是显而易见的,刚才提到,在执行monitorenter指令时,如果这个对象没有锁定,或者当前线程已经拥有了 这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器+1,其实本质上就通过这种方式实现了可重入性。

为 什 么 说 Synchronized 是 非 公 平 锁 ?

非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁 ,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。

为 什 么 说 Synchronized 是 一 个 悲 观 锁 ? 乐 观 锁 的 实 现 原 理 又 是 什 么 ? 什 么 是 CAS, 它 有 什 么 特 性 ?

Synchronized显然是一个悲观锁,因为它的并发策略是悲观的:

不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略。

先进行操作,如果没有其他线程征用数据,那操作就成功了;
如果共享数据有征用,产生了冲突,那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。
乐观锁的核心算法是CAS(Compareand Swap,比较并交换),它涉及到三个操作数:

  • 内 存 值 、 预 期 值 、 新 值 。

当且仅当预期值和内存值相等时才将内存值修改为新值 。

这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。

CAS具有原子性,它的原子性由 CPU 硬件指令实现保证,即使用JNI调用Native方法调用由C++ 编写的硬件级别指令,JDK中提供了Unsafe 类执行这些操作。

AQS 框 架 是 怎 么 回 事 儿 ?

AQS( AbstractQueuedSynchronizer 类 )是一个用来构建锁和同步器的框架,各种 Lock 包中的锁( 常用的有 ReentrantLock、ReadWriteLock),以及其他如 Semaphore、CountDownLatch,甚至是早期的FutureTask等,都是基于AQS来构建。

  • AQS在内部定义了一个volatile int state 变量,表示同步状态:当线程调用lock方法时,如果 state=0,说明没有任何线程占有共享资源的锁 ,可以获得锁并将state=1;如果 state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。

  • AQS通过Node内部类构成的一个双向链表结构的同步队列,来完成线程获取锁的排队工作,当有线程获取锁失败后,就被添加到队列末尾 。

  • o Node 类是对要访问同步代码的线程的封装,包含了线程本身及状态叫waitStatus(有五种不同取值,分别表示是否被阻塞,是否等待唤醒,是否已经被取消等),每个Node结点关联其prev结点和next结点,方便线程释放锁后快速唤醒下一个在等待的线程,是一个FIFO的过程 。

  • o Node类有两个常量,SHARED和EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式是一个锁允许多条线程同时操作(信号量Semaphore 就是基于AQS的共享模式实现的),独占模式是同一个时间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待(如ReentranLock)。

  • AQS 通内部类 ConditionObject 构建等待队列(可有多个),当Condition 调用wait()方法后,线程将会加入等待队列中,而当Condition调 用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。

  • AQS 和 Condition 各自维护了不同的队列,在使用 Lock 和Condition 的时候,其实就是两个队列的互相移动。

附:

我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
有需要的小伙伴可以加群1149778920 暗号:qf
欢迎来到“多核时代”!多线程,并发锁?今天就来带你解析看看什么是“多线程、并发锁”
欢迎来到“多核时代”!多线程,并发锁?今天就来带你解析看看什么是“多线程、并发锁”

欢迎来到“多核时代”!多线程,并发锁?今天就来带你解析看看什么是“多线程、并发锁”