编辑
2024-01-04
面试题库
0

在 JVM 的 synchronized 重量级锁涉及到操作系统(如 Linux)内核态下的互斥锁(Mutex)的使用,其线程阻塞和唤醒都涉及到进程在用户态和到内核态频繁切换,导致重量级锁开销大、性能低。而 JVM 的 synchronized 轻量级锁使用 CAS(Compare and Swap)进行自旋抢锁,CAS 是CPU 指令级的原子操作,并处于用户态下,所以 JVM 轻量级锁开销较小。

操作系统层面的 CAS 是一条 CPU 的原子指令(cmpxchg 指令),正是由于该指令具备了原子性,所以使用 CAS 操作数据时不会造成数据不一致问题,Unsafe 提供的 CAS 方法,直接通过native 方式(封装 C++代码)调用了底层的 CPU 指令 cmpxchg。

Unsafe 类是一个“final”修饰的不允许继承的最终类,而且其构造函数是 private 类型的方法,因此我们无法在外部对 Unsafe 进行实例化,那么怎么获取 Unsafe 的实例呢?可以通过反射的方式。

编辑
2024-01-03
面试题库
0

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。当多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进,这种情况就是死锁。很显然,如果没有外力的作用,那么死锁涉及到的各个进程都将永远处于封锁状态。

死锁产生的四个必要条件

互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放

请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。

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

循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞

避免死锁的几个常见方法

避免一个线程同时获取多个锁。

避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。

对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

编辑
2024-01-02
面试题库
0

Java内存模型(Java Memory Model,JMM)是一种抽象的概念,它定义了Java虚拟机(JVM)在计算机内存中如何执行操作,尤其是在并发编程中,如何处理多线程之间的交互、同步以及数据一致性问题。JMM解决了并发编程中的内存可见性、原子操作、有序性等问题,并提供了一套机制来控制这些行为。

以下是Java内存模型的几个主要组成部分:

主内存与工作内存

在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

Java线程之间的通信由Java内存模型(简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

编辑
2023-11-17
面试题库
0

AQS(AbstractQueuedSynchronizer)是Java中java.util.concurrent包中提供的一个用于构建锁和其他同步组件的框架。它是很多同步控制类的基础,比如ReentrantLock、Semaphore、CountDownLatch、ReadWriteLock等都是基于AQS来实现其同步功能的。

AQS的原理和组件

AQS使用一个整型的volatile变量(称为state)来表示同步状态,并且提供了一系列用于管理这个状态的方法。它的核心思想是,如果一个线程请求的操作需要等待一段不确定的时间,那么这个线程应该被阻塞,并且放入到AQS内部维护的一个等待队列中。以下是AQS的主要组件和原理:

1、同步状态(State):这个状态是由一个volatile int变量表示的,可以表示资源的数量、锁的持有情况等。同步器基于该状态进行控制。

2、节点和队列:在AQS中,每一个等待的线程都会被包装成一个节点(Node)并添加到队列中。AQS底层维护了一个CLH队列来管理所有等待的线程,一个线程节点的入队和出队是通过一种无锁的方式进行的,即CAS操作。

3、排它锁和共享锁:AQS支持两种同步方式——排它(Exclusive)和共享(Shared)。排它锁模式下,每次只有一个线程能够执行;共享锁模式下,多个线程可同时执行。

4、获取和释放方法:AQS定义了一系列方法来管理同步状态,包括acquire、release、acquireShared、releaseShared等。这些方法会调用同步器实现的tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared方法来了解是否应该授予线程同步状态。

5、阻塞和唤醒:无法获取同步状态的线程将会被阻塞。AQS使用LockSupport提供的park和unpark方法来实现线程的挂起和唤醒。

AQS的工作流程

编辑
2023-11-16
面试题库
0

AQS(AbstractQueuedSynchronizer)的状态(State)是其实现同步控制的核心,它用一个int类型的变量来表示。这个状态对于不同的同步组件有不同的含义。例如,在ReentrantLock中,状态可以表示锁被持有的次数;在CountDownLatch中,状态可以表示计数器的当前值;而在Semaphore中,状态表示可用的信号量。

状态的工作原理

1、原子性更新: AQS使用CAS(比较并交换)操作来保证状态值的原子性更新,确保在多线程环境中的安全性。CAS操作涉及三个主要操作数:内存位置(在这里是状态变量)、预期原值和新值。只有当内存位置的当前值等于预期值时,才会将内存位置的内容替换为新值。

2、获取与释放同步状态: AQS提供了一系列用于获取和释放同步状态的方法,不同的同步组件会根据其具体需求来实现这些方法。例如,tryAcquire和tryRelease方法分别用于尝试获取和释放独占式的同步状态,而tryAcquireShared和tryReleaseShared方法用于共享式同步状态。

3、状态的含义与使用: 状态的具体含义根据使用AQS的同步组件的需求而定。组件通过重写AQS提供的方法来定义状态的使用逻辑。例如,对于独占锁,该状态可能表示持有锁的线程数;对于计数器或信号量,该状态表达当前可用资源的数量。

4、同步状态的转换: 同步组件通过调用AQS的方法来改变同步状态,以实现其同步逻辑。当状态改变引发线程的唤醒或阻塞时,AQS将负责进行线程管理,遵循先进先出(FIFO)的原则,从等待队列中唤醒或加入线程。

实例

以ReentrantLock为例,当线程尝试获取锁时,它会使用CAS操作尝试更新状态值从0变为1,表示锁被一个线程持有。如果该操作成功,锁的拥有权就被赋予了该线程。如果锁已经被持有(状态值非0),且当前线程是锁的持有者,则状态值会增加(重入次数加1)。释放锁时,线程会减少状态值。当状态值回到0时,表示锁被完全释放。