Java八股文

基础

String,StringBuilder,StringBuffer的区别

String是不可变的,因此线程安全,但是在频繁拼接常见浪费内存,StringBuilder是可变的,StringBuffer在方法操作加了synchronized,因此是线程安全的

ArrayList,LinkedList的区别和使用场景

ArrayList底层是动态数组,便于按下标O(1)查找。LinkedLiist底层是双向链表,便于插入跟删除。
如果需要 频繁的随机访问 和 尾部插入/删除操作,优先选择 ArrayList。
如果需要 频繁的插入/删除操作 和 实现队列功能,优先选择 LinkedList。
对于 内存占用敏感 的场景,ArrayList 更合适;而对于 数据动态变化频繁 的场景,LinkedList 更合适。

HashMap原理,怎么解决hash碰撞的,HashMap在多线程情况下会有什么问题

HashMap 是 Java 中基于哈希表实现的键值对存储结构,使用键的 hashCode() 方法生成哈希值,然后通过某种算法(如取模)将哈希值映射到桶的索引范围内。
哈希冲突是指两个不同的键通过哈希函数映射到同一个桶的情况。HashMap 主要通过以下两种方式解决冲突:
2.1 链表法
当多个键映射到同一个桶时,这些键值对会以链表的形式存储在该桶中。
查找时,通过遍历链表来找到目标键值对。
2.2 红黑树优化
在 Java 8 中引入了红黑树优化:
如果链表长度超过 8,链表会被转换为红黑树。
红黑树是一种自平衡的二叉搜索树,能够将查找、插入和删除操作的时间复杂度从 O(n) 优化到 O(log n)。
当链表长度减少到 6 或更少时,红黑树会被转换回链表。

HashMap 在多线程环境下可能会出现以下问题:
3.1 数据不一致
HashMap 不是线程安全的。在多线程环境下,多个线程同时修改 HashMap 时,可能会导致数据不一致。
3.2 死循环
在多线程环境下,HashMap 的扩容操作可能会导致死循环:
当多个线程同时触发扩容操作时,可能会出现链表节点的循环引用,导致链表变成环形结构。
在遍历链表时,程序会陷入死循环。

ConcurrentHashMap

jdk1.8对并发方面的优化

  1. ConcurrentHashMap 的优化
    在 JDK 1.8 中,ConcurrentHashMap 的设计和实现发生了重大变化,主要包括以下几个方面:
    1.1 数据结构的改进
    取消分段锁(Segment):JDK 1.7 中的 ConcurrentHashMap 使用分段锁机制,将哈希表分为多个段(Segment),每个段是一个独立的小哈希表。然而,这种设计在高并发场景下仍然存在锁竞争的问题。JDK 1.8 改用了更细粒度的锁机制,直接对数组中的每个桶(bucket)加锁。
    引入红黑树:当链表长度超过 8 时,链表会被转换为红黑树,从而将查找、插入和删除操作的时间复杂度从 O(n) 优化到 O(log n),显著提升了性能。
    1.2 锁机制的改进
    CAS + synchronized:JDK 1.8 使用了 CAS(Compare-And-Swap)操作和 synchronized 关键字相结合的方式。CAS 用于无锁的原子操作,而 synchronized 仅在对链表或红黑树的头节点进行操作时使用,进一步减少了锁竞争。
    1.3 性能提升
    更细粒度的锁:锁的粒度从 JDK 1.7 的分段锁变为 JDK 1.8 的单个桶锁,大大减少了锁竞争,提升了并发性能。
    动态扩容:扩容操作更加高效,通过分段扩容的方式避免了对整个哈希表的锁定。
  2. 其他并发工具的优化
    除了 ConcurrentHashMap,JDK 1.8 还对其他并发工具进行了优化和改进:
    2.1 并行流(Parallel Streams)
    JDK 1.8 引入了并行流(java.util.stream 包),允许开发者以声明式的方式处理集合数据。并行流通过 Fork/Join 框架实现,能够自动将任务分解为多个子任务并行执行,从而充分利用多核处理器的计算能力。
    2.2 StampedLock 的引入
    JDK 1.8 引入了 StampedLock,它是一种更灵活的锁机制,支持读写锁的升级和降级。与传统的 ReentrantReadWriteLock 相比,StampedLock 提供了更高的灵活性和性能。
    2.3 线程池的优化
    JDK 1.8 对线程池(java.util.concurrent.Executor)进行了优化,提供了更灵活的配置和监控机制。开发者可以通过线程池的参数调整和监控工具,更好地管理线程资源,避免线程过多或过少导致的性能问题。

CAS

  1. CAS 的基本概念
    CAS(Compare-And-Swap)是一种无锁的原子操作,用于在并发环境下实现线程安全。它包含三个关键部分:
    V:要更新的变量(var)。
    E:预期值(expected)。
    N:新值(new)。
    CAS 的操作过程如下:
    检查变量 V 的当前值是否等于预期值 E。
    如果等于,将 V 的值更新为新值 N。
    如果不等于,说明变量已被其他线程修改,当前操作失败
    @ForceInline
    public final boolean compareAndSwapObject(Object o, long offset,Object expected,Object x) {
        return theInternalUnsafe.compareAndSetReference(o, offset, expected, x);
    }
    @ForceInline
    public final boolean compareAndSwapInt(Object o, long offset,int expected,int x) {
        return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
    }
    @ForceInline
    public final boolean compareAndSwapLong(Object o, long offset,long expected,long x) {
        return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
    }
  1. CAS 的应用场景
    CAS 是一种乐观锁机制,适用于“读多写少”的场景。它通过无锁操作减少了线程阻塞和唤醒的开销,从而提高了并发性能。例如,java.util.concurrent.atomic 包中的 AtomicInteger 和 AtomicLong 等类就是基于 CAS 实现的。
  2. CAS 的优点
    无锁操作:避免了传统锁的线程阻塞和唤醒开销。
    高并发性能:适合读多写少的场景。
    原子性:保证了单个变量操作的原子性。
  3. CAS 的问题及解决方案
    尽管 CAS 提供了高效的并发控制,但它也存在一些问题:
    ABA 问题:一个变量从 A 变为 B,再变回 A,CAS 无法检测到这种变化。
    解决方案:使用 AtomicStampedReference,通过版本号或时间戳解决 ABA 问题。
    长时间自旋:如果 CAS 操作长时间失败,会导致大量 CPU 资源浪费。
    解决方案:引入 pause 指令,让 CPU 在自旋失败时短暂休眠。
    多个共享变量的原子操作:CAS 只能保证单个变量的原子性。
    解决方案:使用锁或 AtomicReference 来保证多个变量的原子性。

并发

线程池底层原理,调度丢弃策略有哪些 ,线程池有哪些参数

Mysql

MySql存储引擎有哪些,区别

explain输出结果有哪些

文章作者: 热心网民詹Sir
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 编程之家
java java
喜欢就支持一下吧