【Java】Java核心要点总结:60

简介: 1. 乐观锁一定就是好的吗乐观锁并不一定好,它有自己的适用场景和局限性。乐观锁的优点在于在低并发环境下表现良好,操作基本上都能够成功,不会阻塞其他线程的执行。此外,乐观锁没有锁带来的资源竞争和多线程间的上下文切换开销,对于高并发的场景下可以提供更好的性能表现。但是,在高并发场景下乐观锁的表现往往受到影响,因为多个线程同时执行操作和检查比较,会出现重试次数增加的情况,进而增加了延迟和开销,降低了系统的吞吐量和性能。此时,悲观锁可能比乐观锁更适合解决问题,因为它能够阻塞其他线程,等待当前线程释放锁后再执行。

1. 乐观锁一定就是好的吗


乐观锁并不一定好,它有自己的适用场景和局限性。


乐观锁的优点在于在低并发环境下表现良好,操作基本上都能够成功,不会阻塞其他线程的执行。此外,乐观锁没有锁带来的资源竞争和多线程间的上下文切换开销,对于高并发的场景下可以提供更好的性能表现。


但是,在高并发场景下乐观锁的表现往往受到影响,因为多个线程同时执行操作和检查比较,会出现重试次数增加的情况,进而增加了延迟和开销,降低了系统的吞吐量和性能。此时,悲观锁可能比乐观锁更适合解决问题,因为它能够阻塞其他线程,等待当前线程释放锁后再执行。


因此,选择使用何种锁策略需要根据实际情况进行综合考虑,包括需求、应用场景、并发程度以及编写代码的复杂程度等多个因素。在实际开发中也可以尝试将两种锁策略结合起来使用,根据情况动态选择最适合的策略,提升代码的效率和性能表现。

2. Synchronized和ReentrantLock有什么异同

Synchronized 和 ReentrantLock 在实现线程同步的过程中是很类似的,它们都可以通过获得锁控制并发访问,但也有一些不同点:


相关性:Synchronized 是 Java 语言内置的关键字,因此使用起来非常简便;而 ReentrantLock 是一个基于 Java API 的类,需要手动创建和管理。


获取方式:Synchronized 锁时很容易获取的,在使用 synchronized 关键字修饰方法或代码块时,都自动获得了对象的锁;而 ReentrantLock 需要显式地调用 lock() 锁定资源,并且在使用后还需要 unlock() 解锁,否则就会发生死锁等问题。


可扩展性:ReentrantLock 拥有 Synchronized 不具备的额外功能,比如支持可重入锁、公平锁和非阻塞锁等。


性能差异:相较于 Synchronized,ReentrantLock 执行效率更快,特别是在遇到高并发场景时,ReentrantLock 更容易保证线程执行的顺序。但是,当并发量较小的时候,Synchronized 的效率可能会更高,此外 Synchronized 内部包装了锁的升级降级机制,使得效率在低竞争、低并发的情况下也比较高。


在选择 Synchronized 和 ReentrantLock 的时候,需要根据具体的实现场景和需求来选择合适的锁策略,两者都有其优势和适用场地,不能单纯的从性能方面进行取舍判定。并且,在锁的使用中一定要注意避免死锁等问题的发生,保证线程安全。

3. ReentrantLock是如何实现可重入性的

ReentrantLock 实现可重入性的核心在于内部维护了一个 holdCount 属性,用于记录锁被当前线程持有的次数。初次获得锁时,这个值为 1,每次进入同步块时加 1,退出时减 1。只有当 holdCount 的值为 0 时,表示锁已被完全释放。

实现代码大致如下:

public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        lock.lock();
        try {
            // do something
            method2();
        } finally {
            lock.unlock();
        }
    }
    private void method2() {
        lock.lock();
        try {
            // do something
        } finally {
            lock.unlock();
        }
    }
}

在上面的代码中,method() 方法获取了锁后又调用了 method2() 方法,而 method2() 方法也需要获取同一个锁。因为当前线程已经获得了该锁,所以可以再次获取,这就是 ReentrantLock 可重入的原因。


此外,ReentrantLock 还有许多高级特性,比如公平锁、读写锁等,它们都基于可重入锁的思想衍生而来,充分利用了 ReentrantLock 所提供的灵活性和可扩展性。

4. 和Synchronized相比,可以重入锁ReentrantLock实现原理有什么不同


和 Synchronized 相比,重入锁 ReentrantLock 的实现原理有一些不同:


底层实现:ReentrantLock 是基于 Java API 实现的类,而 Synchronized 则是语言级别支持的关键字。在底层实现上,ReentrantLock 基于 AbstractQueuedSynchronizer (AQS)来实现线程同步,而 Synchronized 则是直接依赖 JVM 实现的。


获得和释放锁的方式:ReentrantLock 提供了 lock() 和 unlock() 方法,用于手动获取和释放锁,同时也提供 tryLock() 方法来尝试获得锁;而 Synchronized 则是由 JVM 自动管理的,当线程进入 synchronized 修饰的代码块时自动获取监视器锁,退出时自动释放。


重入性:ReentrantLock 可以支持可重入锁机制,即线程可以多次获取同一把锁,而 Synchronized 已经默认支持了重入锁机制。


可定制性:ReentrantLock 有更高的可定制性,如公平锁、非公平锁、可实现读写分离等特殊场景下需要使用的扩展功能;而 Synchronized 则不具备这些扩展功能。


性能表现:在低竞争、低并发的情况下,Synchronized 不比 ReentrantLock 差,在高并发、高竞争的情况下,ReentrantLock 的性能优于 Synchronized。


ReentrantLock 和 Synchronized 都可以用于实现线程同步,但 ReentrantLock 的可定制性和可重入性底层基于 AQS 实现,在某些高并发场景下表现更优;而 Synchronized 的使用成本低,实现简单,适合在一些简单场景下使用。

5. AQS框架

AQS(AbstractQueuedSynchronizer)是一个提供了一个简单框架用于实现阻塞锁和相关的同步器的抽象类。


AQS 的核心思想就是通过一个双向队列来完成线程的排队和等待,它维护了一个 Volatile 的 state 变量,可以理解为 status。state 的修改只能由持有独占锁的线程完成,其他线程可以通过 CAS 操作竞争修改它,以此控制对共享资源的访问和保证线程间的互斥访问。


通过这种方式,AQS 屏蔽了大部分与并发操作相关的细节,并提供了基础的同步操作,如 acquire(获取锁) 和 release(释放锁),同时还可以扩展出诸如 Semaphore、CountDownLatch、ReentrantLock 等高级同步器,这些同步器都是基于 AQS 框架实现的。


在使用 AQS 时,开发者需要负责编写 acquire 和 release 方法来实现同步控制逻辑,通常会通过一个内部类来代表各个需要同步的状态,如 Sync 类在 ReentrantLock 中就代表了_HOLD、_WAITING 和 _NONE 三个状态,然后针对不同的状态下具体的执行逻辑编写相应的 acquire 和 release 方法,在 acquire 方法中尝试获取锁,如果失败则加入等待队列并挂起自己,等到获取到锁或者被中断时再次尝试获取并执行相应的操作;在 release 方法中释放锁并唤醒等待队列中的线程。


AQS 是一个基于队列和状态变量实现的同步框架,屏蔽了底层调度和同步机制的细节,提供了一套标准的用于实现阻塞锁和同步器的 API,并通过各种扩展支持了更多高级同步器的实现。

相关文章
|
安全 Java 数据库连接
【Java】Java核心要点总结:62
1. 线程中的线程是怎么创建的,是一开始就随着线程池的启动创建好的吗? 线程中的线程通常是通过在父线程中创建新的Thread对象并将其加入线程池中实现的。当然,这个过程也可以通过一些其他的方式来实现,比如使用ExecutorService.submit()方法提交一个Callable或Runnable任务。
|
缓存 安全 Java
【Java】Java核心要点总结70
1. volatile 如何保证变量的可⻅性? 在Java中,使用volatile关键字可以确保变量的可见性。 当一个线程修改了一个被volatile修饰的变量时,它会立即将该变量的最新值刷新到主内存。而其他线程在读取该变量时,会从主内存中重新获取最新值,而不是使用缓存中的旧值。 这样做的原因是,普通的变量在多线程环境下存在线程间不可见的问题。每个线程都有自己的工作内存,由于运行速度快和编译优化等原因,线程可能会直接读取工作内存中的旧值,而不去主内存中获取最新的值,导致线程之间的数据不一致。
|
3月前
|
消息中间件 NoSQL Java
Java知识要点及面试题
该文档涵盖Java后端开发的关键知识点,包括Java基础、JVM、多线程、MySQL、Redis、Spring框架、Spring Cloud、Kafka及分布式系统设计。针对每个主题,文档列举了重要概念及面试常问问题,帮助读者全面掌握相关技术并准备面试。例如,Java基础部分涉及面向对象编程、数据类型、异常处理等;JVM部分则讲解内存结构、类加载机制及垃圾回收算法。此外,还介绍了多线程的生命周期、同步机制及线程池使用,数据库设计与优化,以及分布式系统中的微服务、RPC调用和负载均衡等。
|
4月前
|
Java
【Java基础面试三十七】、说一说Java的异常机制
这篇文章介绍了Java异常机制的三个主要方面:异常处理(使用try、catch、finally语句)、抛出异常(使用throw和throws关键字)、以及异常跟踪栈(异常传播和程序终止时的栈信息输出)。
|
4月前
|
Java
【Java基础面试十六】、Java中的多态是怎么实现的?
这篇文章解释了Java中多态的实现机制,主要是通过继承,允许将子类实例赋给父类引用,并在运行时表现出子类的行为特征,实现这一过程通常涉及普通类、抽象类或接口的使用。
|
缓存 监控 Java
【Java】Java核心要点总结:61
1. java中的线程池是如何实现的 Java 中的线程池是通过 ThreadPoolExecutor 类实现的。ThreadPoolExecutor 继承自 AbstractExecutorService,并实现了 Executor、ExecutorService 和 Future 接口,它可以为程序提供管理和控制多个工作者线程的功能,提高线程重用性并避免线程频繁创建销毁的开销。
|
监控 安全 Java
Java面试题-Java核心基础
Java面试题-Java核心基础
106 2
|
算法 安全 Java
【Java】Java核心要点总结:58
1. java中 怎么确保一个集合不能被修改 Java 中可以使用 Collections 类的 unmodifiableXXX() 方法来确保一个集合不能被修改。其中,XXX 表示需要被转换的集合类型,如 List、Set、Map 等。这些方法都返回指定集合的不可修改视图
|
算法 安全 Java
【Java】Java核心要点总结:59
1. 线程的run()和start()有什么区别,为什么不直接调用run() Java中通过继承 Thread 或实现 Runnable 接口来创建线程,线程是通过 start() 方法启动的,而不是直接调用 run() 方法。下面是它们之间的区别: start() 和 run() 的区别 调用 start() 方法会启动一个新线程并执行其中的 run() 方法,而直接调用 run() 方法将在当前线程中执行 run() 方法,并不会创建新的线程。
|
存储 Java C语言
【Java】Java核心要点总结 67
1. 浮点数运运算会有精度损失 这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
下一篇
DataWorks