全新Java面试题及答案(面试必看)

1、Java中的集合类有哪些?说说他们的特点?

List(列表)
特点:有序、可重复

  • ArrayList:基于数组实现的动态数组,可以根据需要自动增长容量。它提供了快速的随机访问和在末尾添加/删除元素的性能。适合需要频繁随机访问元素,以及需要在末尾进行添加/删除操作的场景。
  • LinkedList:基于双向链表实现的列表,可以高效地在任意位置进行添加/删除操作。它提供了快速的插入和删除性能。
  • Vector:与ArrayList类似,但它是线程安全的,因此性能通常较低。现在较少使用,更多地被ArrayList和CopyOnWriteArrayList替代。

Set(集合)
特点:无序、不可重复

  • HashSet:基于哈希表实现的集合,不允许重复元素。它提供了快速的添加、删除和查找性能。
  • TreeSet:基于红黑树实现的有序集合,可以按照自然顺序或自定义顺序对元素进行排序。它提供了快速的查找和有序遍历性能。
  • LinkedHashSet:保持元素插入顺序的HashSet,基于LinkedHashMap实现。

Queue(队列)
特点:先进先出

  • LinkedList:除了作为列表使用,LinkedList还可以用作队列,实现FIFO(先进先出)的数据结构。
  • PriorityQueue:基于优先级堆的无界队列,元素按照其自然顺序或者创建PriorityQueue时所提供的Comparator进行排序。
  • ArrayDeque:一个由数组结构提供的双端队列,可以作为栈使用。

Map(映射)
特点:键值对存储、无序

  • HashMap:基于哈希表实现的键值对映射,不允许重复键。它提供了快速的添加、删除和查找性能。
  • TreeMap:基于红黑树实现的键值对映射,按键的自然顺序或自定义顺序对键值对进行排序。
  • ConcurrentHashMap:基于分段锁实现的并发哈希表,支持高并发的读取和部分并发的写入操作。
  • Hashtable:与HashMap类似,但它是线程安全的,并且不允许键或值为null。现在较少使用,更多地被ConcurrentHashMap替代。

2、集合的排序方式的实现方案?

List自带的 sort 方法、集合工具类 Collections 下面的sort方法、stream 流中的 sorted方法

区别:

List.sort:
List.sort 是 List 接口的默认方法,可以直接在列表对象上调用。它也是一个对原始列表进行排序的原地排序方法,可以使用自定义的 Comparator 进行排序。

Collections.sort:
Collections.sort 是对实现了 List 接口的集合进行排序的静态方法。它实际上会调用列表对象的 sort 方法来完成排序操作。

list.stream().sorted:
list.stream().sorted 是使用流的排序方法,它会产生一个新的经过排序的流,可以在后续收集为一个列表或执行其他操作。
如果使用 sorted(null),则会使用默认的自然排序进行排序。

性能对比:

在大多数情况下,原地排序的方法(Collections.sort 和 List.sort)的性能会优于使用流进行排序(list.stream().sorted)。
原地排序方法直接在原始列表上进行操作,不需要额外的内存分配,因此速度更快。
流排序方法(list.stream().sorted)通常会引入额外的内存和计算,因此在速度和性能上可能会略逊于原地排序。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

3、ArrayList、LinkedList与Vector的区别?

1. 数据结构:

  • ArrayList 基于数组实现。
  • LinkedList 基于双向链表实现。
  • Vector 也基于数组实现。

2. 线程安全性:

  • ArrayList是非线程安全的。

  • LinkedList是非线程安全的。

  • Vector是线程安全的。

3. 性能:

  • 随机访问时,ArrayList性能较好。

  • 频繁插入和删除时,LinkedList性能较好。

  • Vector在某些情况下性能可能相对较低。

4. 扩容机制:

  • ArrayList和Vector都有扩容机制,ArrayList是1.5倍扩容,Vector是两倍扩容。
  • LinkedList是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。

4、HashMap、Hashtable和ConcurrentHashMap的区别?

1. 线程安全性:

  • HashMap 是非线程安全的。
  • Hashtable 是线程安全的,但效率较低。
  • ConcurrentHashMap 是线程安全的,支持高并发环境。

2. 性能:

  • 一般情况下,HashMap 的性能较好,Hashtable 由于线程安全的实现,性能相对较差,ConcurrentHashMap
    在保证线程安全的同时,性能也比较优秀。

3. null 值:

  • HashMap 允许键和值为 null。

  • Hashtable 不允许键和值为 null。

  • ConcurrentHashMap 允许键为 null,但不允许值为 null。

5、HashMap的初始化,put流程,get流程,扩容流程说明

初始化:

  • HashMap 在创建时可以指定初始容量和负载因子。
  • 如果没有指定,默认初始容量为 16,负载因子为 0.75。

put 流程:

  • 计算键的哈希值。

  • 根据哈希值确定存储位置(通过取模运算)。

  • 如果该位置没有元素,直接插入。

  • 如果该位置已有元素,判断是否与要插入的键相等,若相等则更新值;否则形成链表结构。

get 流程:

  • 计算键的哈希值。

  • 根据哈希值找到对应的存储位置。

  • 在该位置遍历链表或直接返回对应的值。

扩容流程:

  • 当元素数量超过当前容量与负载因子的乘积时,进行扩容。
  • 扩容时,创建一个新的容量为原来两倍的数组,并将原数组中的元素重新哈希并迁移到新数组中。

、HashMap、HashSet、ArrayList是线程安全的吗?

HashMap、HashSet和ArrayList都不是线程安全的。

6、创建线程的几种方式?

继承Thread类、实现Runnable接口、使用线程池

7、线程同步的方式有哪些方式?

synchronized关键字、加锁、volatile、原子类

在这里插入图片描述

8、synchronized如何使用?加在普通方法上和加在静态方法的区别?

当 synchronized 关键字加在普通方法上时,它会锁定对象实例;而加在静态方法上时,它锁定的是类的Class对象。让我通过一个简单的Java类来说明这两种情况。

public class SynchronizedExample {
    // 用于演示锁定对象实例的普通方法
    public synchronized void synchronizedMethod() {
        // 同步的操作
    }

    // 用于演示锁定类的Class对象的静态方法
    public static synchronized void synchronizedStaticMethod() {
        // 同步的操作
    }
}

现在我们创建两个线程来演示这两种情况:

public class Main {
    public static void main(String[] args) {
        final SynchronizedExample example = new SynchronizedExample();

        // 在普通方法上加锁的示例
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                example.synchronizedMethod();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                example.synchronizedMethod();
            }
        });

        // 在静态方法上加锁的示例
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedExample.synchronizedStaticMethod();
            }
        });

        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedExample.synchronizedStaticMethod();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

在这个例子中,thread1和thread2演示了加在普通方法上的 synchronized 关键字,它们都是针对同一个SynchronizedExample对象实例的。
thread3和thread4演示了加在静态方法上的 synchronized 关键字,它们是针对SynchronizedExample类的Class对象的。
在静态方法上使用 synchronized 关键字时,该关键字锁定的是类的 Class 对象,而不是类的实例对象。这种机制保证了无论类的实例有多少个,同一时刻只能有一个线程执行该类的静态 synchronized 方法,从而确保了对静态方法的同步访问。

springMVC中的service是单例的,因此在service的实现类impl中加 synchronized 关键字,也能使controller层的请求排队。

9、什么情况下需要对对象进行序列化?

对象序列化是一个将对象状态转换为字节流的过程,主要用于实现对象的完全保存或网络传输。当需要满足以下场景时,通常需要对对象进行序列化:

  • 网络传输:在分布式系统中,对象经常需要在不同的进程或机器之间进行传输。为了在网络中有效地传输对象,需要将对象序列化为字节流,以便在接收端进行反序列化并恢复为原始对象。
  • 数据备份和恢复:当需要将对象状态保存在计算机中以备将来使用时,序列化是一种有效的手段。通过序列化,可以方便地将对象保存到文件或数据库中,以便在需要时进行恢复。
  • 持久化存储:序列化是实现对象持久化存储的一种常见方法。通过将对象序列化为字节流并保存到文件中,可以确保对象状态的长期保存,即使程序关闭或重启后也能恢复对象状态。

10、什么是AIO、BIO和NIO?

AIO、BIO和NIO都是IO模型,它们在处理输入输出操作时具有不同的特点和适用场景。

BIO(Blocking I/O):

  • BIO是同步阻塞I/O模型。在BIO方式中,一个连接对应一个线程。当客户端发起连接请求后,服务端就需要启动一个线程进行处理。如果这个连接不做任何事情就会造成不必要的线程开销。这种方式的缺点在于,如果有大量客户端并发请求,就需要创建大量的线程来处理,这将导致系统资源的大量消耗,严重情况下甚至会导致系统崩溃。BIO方式适用于连接数量少且固定的场景。

NIO(Non-blocking I/O):

  • NIO是同步非阻塞IO模型。它引入了通道(Channel)和缓冲区(Buffer)的概念。数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。这种模型允许一个线程要求一个通道将数据读入一个缓冲区,在数据从通道读入缓冲区的期间,这个线程可以进行其他操作,直到数据完成从通道读入到缓冲区时,线程再继续处理它。数据从缓冲区写入通道时也是如此。此外,NIO还引入了选择器的概念,使得一个线程可以监听多个通道的数据传输情况。NIO适用于连接数目多且业务比较轻的场景,如聊天服务器。

AIO(Asynchronous I/O):

  • AIO是异步非阻塞IO模型。在JDK1.7之后,Java提供了异步的相关通道实例。AIO的最大特点是具备异步功能,需要借助操作系统,当底层操作系统具有异步IO模型时,AIO可以在对应的read/write/accept/connection等方法上异步执行,完成后会主动调用回调函数,实现一个CompletionHandler对象。AIO消除了用户态和内核态的切换耗时,使多任务的发展更加容易。此外,它的缓冲机制使得不同的文件读写有更少的线程切换和上下文引起的性能损失。AIO适用于连接数目多且连接比较长(业务重操作)的场景,需要操作系统充分参与并发操作。

11、volatile能保证原子性吗?为什么?

不可以,volatile 关键字能够保证变量的可见性,但是不能保证原子性。
在这里插入图片描述

在没有使用 volatile 关键字的情况下,当一个线程修改了变量的值,这个修改之后的值会先被保存在线程的工作内存中,并不会立即刷新到主内存中。这是由于 CPU 和内存优化的特性所决定的。

数据最终会从线程的工作内存刷新到主内存中,但具体刷新的时间并没有明确定义。这取决于底层的架构、CPU 的具体行为,以及 JVM 和编译器应用的各种优化。

12、JUC并发包常用的工具类,分别有什么特性,适用场景?

JUC(java.util.concurrent)是Java中的一个重要包,它提供了一系列用于解决并发问题的工具类。主要包括atomic(原子类)、locks(锁)和一些并发类(如线程安全类和线程池相关:ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、Executors、CompletableFuture等)
以下是JUC并发包中常用的一些工具类及其特性和适用场景:

ReentrantLock:

  • 特性:是一个可重入的互斥锁,支持公平锁和非公平锁。通过它,可以灵活地控制多个线程对共享资源的访问。
  • 适用场景:适用于需要实现复杂同步控制的场景,比如需要手动控制锁的获取和释放,或者需要实现更细粒度的锁控制。

Semaphore:

  • 特性:俗称信号量,用于控制同时访问某个特定资源的线程数量。通过它,可以实现流量控制,防止过多的线程同时访问某个资源。
  • 适用场景:适用于需要对资源进行访问控制的场景,比如数据库连接池、线程池等,确保资源不会被过度消耗。

CountDownLatch:

  • 特性:一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。当计数器的值减至0时,等待的线程会被唤醒。
  • 适用场景:适用于需要等待一组线程完成某项操作后才能继续执行的场景,比如启动多个线程进行并行计算,然后等待所有线程计算完成后进行汇总。

CyclicBarrier:

  • 特性:一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(common barrier point)。
  • 适用场景:适用于需要将一组线程划分为几个阶段执行,并且每个阶段都需要所有线程都完成后才能进入下一个阶段的场景。

Exchanger:

  • 特性:用于两个线程之间交换数据。当一个线程进入交换时,它会等待另一个线程也进入交换,然后这两个线程交换数据。
  • 适用场景:适用于两个线程需要互相交换数据的场景,比如生产者消费者模型中,生产者和消费者需要交换数据。

     篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处即可】免费获取

ConcurrentHashMap:

  • 特性:一个线程安全的HashMap实现,它支持高并发读写操作。通过分段锁技术,实现了高效的并发性能。
  • 适用场景:适用于需要存储大量键值对,并且需要支持高并发读写的场景,比如缓存系统、分布式系统等。

13、SimpleDateFormat线程安全性?

SimpleDateFormat 类在 Java 中是非线程安全的。这意味着如果多个线程同时共享同一个 SimpleDateFormat 实例,并尝试使用它进行日期格式化或解析,可能会遇到不可预测的结果和并发问题。

SimpleDateFormat 的非线程安全性主要源于其内部状态(如日期字段、数字等)在格式化或解析过程中可能会被修改。如果多个线程同时访问这些内部状态,就可能导致数据竞争和不一致的行为。

为了解决这个问题,有几种常见的做法:

  • 每个线程使用自己的 SimpleDateFormat 实例:这是最简单且最直接的方法。通过为每个线程分配一个单独的
    SimpleDateFormat 实例,可以避免线程间的数据竞争。然而,这可能会增加内存消耗,特别是在高并发场景下。
  • 使用同步块或锁:通过在访问 SimpleDateFormat 实例时添加同步块或锁,可以确保每次只有一个线程能够修改其内部状态。这可以减少内存消耗,但可能会降低性能,因为线程需要等待锁的释放。
  • 使用线程安全的替代方案:Java 8 引入了新的日期和时间 API(如 java.time.format.DateTimeFormatter),这些 API 是线程安全的。如果可能的话,考虑使用这些新的 API
    来替代 SimpleDateFormat。
  • 使用第三方库:有些第三方库提供了线程安全的日期格式化功能。这些库通常经过优化,可以在高并发环境下提供更好的性能。

14、AQS和CAS分别是什么,如何理解?

AQS是一个Java提供的底层同步工具类,用于构建锁和同步器的框架性组件。它是Java并发包中ReentrantLock、Semaphore、ReentrantReadWriteLock等同步器的基础。AQS的主要特点包括支持独占模式和共享模式,并为这些同步器提供了一个统一的基础框架,让开发人员可以基于此进行扩展和定制化。通过使用AQS,开发人员可以避免自己重复实现同步器的底层机制,从而更加专注于业务的实现。此外,AQS还提供了高效的并发性能,适用于实现计数器、累加器、分布式数据同步、并发队列、内存管理以及自旋等待机制等各种场景。

CAS是一种无锁技术,涉及三个操作数——内存位置(V)、预期原值(A)和新值(B)。当且仅当内存位置V的值等于预期原值A时,将内存位置V的值设置为新值B。否则,处理失败,什么都不做。一般情况下是一个自旋操作,即不断地重试。CAS操作具有原子性,它在多线程环境下可以保证对一个变量的操作过程中不会被其他线程干扰。CAS操作常用于实现高性能的并发队列、内存管理和自旋等待机制等。

15、 ConcurrentHashMap为什么1.8取消了使用ReentrantLock锁?

ConcurrentHashMap在Java 1.8中取消了使用ReentrantLock锁,主要基于以下几个原因:

  • 减少内存开销:使用ReentrantLock需要节点继承AQS(AbstractQueuedSynchronizer)来获得同步支持,这增加了内存开销。相比之下,Java 1.8中的ConcurrentHashMap采用了更轻量级的同步机制,如CAS(Compare and Swap)和synchronized,减少了节点的内存占用。
  • 优化并发性能:在Java 1.8中,ConcurrentHashMap的存储结构改为了Node数组+链表/红黑树。当冲突链表达到一定长度时,链表会转换成红黑树,以进一步优化查询性能。同时,ConcurrentHashMap对每个Node节点进行加锁,这种细粒度的锁控制可以提高并发度,减少线程间的争用。
  • 简化和统一并发机制:通过采用CAS和synchronized等轻量级同步机制,Java 1.8中的ConcurrentHashMap简化了并发控制逻辑,使得代码更加简洁易懂。同时,这也使得Java的并发机制更加统一和一致。

16、HashMap是如何解决Hash冲突的?解决hash冲突都有哪些方案?

HashMap 解决哈希冲突的主要方法是通过链地址法(Separate Chaining)。当发生哈希冲突时,即不同的键具有相同的哈希值,HashMap 会在哈希表的每个桶(bucket)中维护一个链表(或者在链表长度较长的情况下,可以转换为红黑树)来存储具有相同哈希值的键值对。

17、HashMap和ArrayLsit区别,以及他们的key是否可重复/为空?

在这里插入图片描述

18、平常用过线程池吗?你该注意哪些问题?

应该注意核心线程数、队列、拒绝策略、最大线程数、超时时间这些参数的设置。

19、线程池的等待队列有哪几种实现方式?

在Java的java.util.concurrent包中,线程池框架提供了几种不同的队列实现方式,以适应不同的应用场景和需求。以下是线程池等待队列的一些常见实现方式:

ArrayBlockingQueue:

  • ArrayBlockingQueue是一个基于数组的有界阻塞队列。它按照FIFO(先进先出)的原则对元素进行排序。当试图向一个已满的队列添加元素时,添加操作会被阻塞;当试图从一个空的队列中移除元素时,移除操作也会被阻塞。
  • 适用于已知大概任务量的情况,因为可以预先设定队列容量。

LinkedBlockingQueue:

  • LinkedBlockingQueue是一个基于链表的无界(或指定容量的)阻塞队列。与ArrayBlockingQueue相比,它的容量可以动态增长,但也可以指定一个最大容量。
  • 当队列为空时,获取元素的线程会被阻塞;当队列已满时,尝试添加元素的线程也会被阻塞(如果队列设置了最大容量)。
  • 适用于任务量可能会动态变化的情况,因为队列可以动态扩展。

PriorityBlockingQueue:

  • PriorityBlockingQueue是一个支持优先级的无界阻塞队列。元素按照它们的自然顺序或者通过提供的Comparator进行排序。
  • 适用于任务之间有优先级区分的情况,可以确保优先级高的任务优先得到执行。

     篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处即可】免费获取

SynchronousQueue:

  • SynchronousQueue是一个不存储元素的阻塞队列。每一个插入操作必须等待一个相应的删除操作,反之亦然。它支持公平和非公平两种模式。
  • 适用于高并发且任务处理速度非常快的情况,因为任务几乎是在提交后立即得到处理,不需要在队列中等待。

DelayQueue:

  • DelayQueue是一个支持延时获取元素的无界阻塞队列。队列中的元素必须实现Delayed接口,这允许元素在队列中等待特定的时间才能被取出。
  • 适用于需要定时或延时执行任务的情况,例如定时任务调度。

20、为什么说CAS是乐观锁?底层对应那个类?

CAS(Compare-And-Swap)之所以被称为乐观锁,是因为它在数据更新时持有一种乐观的态度,认为在数据被更新的过程中不会有其他线程来修改它。这与悲观锁(如传统的数据库锁)形成对比,悲观锁总是假设最坏的情况,即在数据被处理时总是会有其他线程来修改它,因此需要在整个数据处理过程中锁定数据。

CAS是一种无锁机制,它通过比较内存中的值与期望值是否相等来确定是否执行交换操作。如果内存中的值与期望值相等,则执行交换操作;否则,不执行。这种机制避免了使用传统锁所带来的性能开销和死锁问题,提高了程序的并发性能。

在CAS的底层实现中,Unsafe类起到了核心作用。Unsafe是CAS的核心类,它提供了直接访问底层操作系统的功能,使得CAS能够在底层进行比较和交换操作。通过Unsafe类中的compareAndSwap方法,CAS能够实现无锁的数据结构,保证并发安全。

请注意,CAS并不是严格意义上的锁,而是通过原子性来保证数据的同步。它不会保证线程同步,而是确保在数据更新期间的一致性。此外,CAS也存在一些潜在的问题,如ABA问题(即在CAS操作期间,一个值可能被其他线程多次修改后又改回原来的值,导致CAS操作无法正确感知数据的实际变动)。

综上所述,CAS之所以被称为乐观锁,是因为它在处理并发数据时持有一种乐观的态度,并通过底层的Unsafe类实现无锁操作,从而提高了程序的并发性能。

21、ThreadLocal有使用过吗?底层原理是什么?使用它该注意什么?有什么问题?

是的,我使用过ThreadLocal。ThreadLocal是Java中一个非常有用的类,它提供了线程本地变量。这些变量不同于它们的正常变量,因为每一个访问这个变量的线程都有其自己独立初始化的变量副本。

底层原理:

  • ThreadLocal的底层实现主要依赖于ThreadLocalMap,这是一个特殊的Map,其键为ThreadLocal对象,值为线程变量的副本。每个Thread都持有一个ThreadLocalMap的引用,这个map被用来存储该线程的本地变量。当线程首次访问一个ThreadLocal变量时,通过ThreadLocal的set或get方法在ThreadLocalMap中为其创建一个新的条目。

使用注意事项:

  • 内存泄漏:由于ThreadLocal的生命周期和线程的生命周期不同,如果不注意及时清理ThreadLocal变量,可能会导致内存泄漏。因此,在不再需要使用ThreadLocal时,应调用remove()方法将其从当前线程中清除,避免线程结束后仍然持有对该变量的引用。
  • 共享变量问题:尽管ThreadLocal为每个线程提供了独立的变量副本,但它并不能解决线程间共享变量的同步问题。如果多个线程共享同一个ThreadLocal变量,需要自行处理线程间的同步操作,确保线程安全。
  • 内部使用慎重:在一些特定的情况下,如使用线程池或者异步任务执行框架,使用ThreadLocal需要格外小心。因为线程池中的线程可能会被复用,如果不正确地处理ThreadLocal变量,可能会导致数据混乱。

存在的问题:

  • 性能问题:虽然ThreadLocal可以提高并发性能,但因为它需要在每个线程中存储变量副本,所以会增加内存消耗。同时,对ThreadLocalMap的访问也需要一定的时间,这可能会影响性能。
  • 使用不当导致的错误:如果在使用ThreadLocal时没有正确地处理变量的初始化和清理,可能会导致数据混乱或内存泄漏等问题。

47、如何设计一个高并发系统?

设计一个高并发系统是一个复杂且需要多方面考虑的任务。以下是一些关键步骤和考虑因素,帮助你设计一个能够处理高并发请求的系统:

1. 需求分析:

  • 明确系统的业务场景和目标。
  • 分析预期的并发量、请求频率和数据量。
  • 识别可能的瓶颈和性能敏感点。

2. 架构设计:

  • 选择合适的架构模式,如微服务、分布式系统等。
  • 设计无状态的服务,以便水平扩展。
  • 使用负载均衡器来分发请求到多个服务实例。

3. 数据库设计:

  • 选择适合高并发的数据库技术,如NoSQL数据库或分布式关系数据库。
  • 设计合理的索引和查询优化策略。
  • 考虑使用读写分离读写、分库分表等技术来分散数据库压力。

4. 缓存策略:

  • 使用缓存来减少数据库访问次数,如Redis或Memcached。
  • 设计合理的缓存失效和更新策略。

5. 异步处理:

  • 对于非实时性要求较高的操作,使用消息队列(如Kafka、RabbitMQ)进行异步处理。
  • 解耦核心逻辑和耗时操作,提高系统响应速度。

6. 并发控制:

  • 使用锁、信号量等机制控制并发访问共享资源。
  • 考虑使用分布式锁来确保跨服务的并发安全。

7. 限流与降级:

  • 设计限流策略,防止系统被过量请求压垮。
  • 实现降级机制,在部分服务不可用时保证整体系统的稳定性。

8. 监控与告警:

  • 部署监控系统,实时收集系统性能数据。

  • 设置告警阈值,及时发现并处理潜在问题。

9. 代码优化:

  • 使用高效的数据结构和算法。
  • 避免不必要的资源占用和内存泄漏。
  • 使用连接池等技术来减少资源创建和销毁的开销。

10. 水平扩展:

  • 设计系统时考虑无状态服务,以便通过增加服务实例来水平扩展系统处理能力。
  • 使用自动化部署和容器化技术(如Docker和Kubernetes)来简化扩展过程。

11. 安全性考虑:

  • 确保系统具备足够的安全措施,如身份验证、授权、数据加密等。
  • 对抗DDoS攻击、SQL注入等常见安全威胁。

     篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处即可】免费获取

12.测试与调优:

  • 进行压力测试和性能测试,确保系统在高并发场景下能够稳定运行。
  • 根据测试结果对系统进行调优,优化性能瓶颈。

在设计高并发系统时,还需要注意以下几点:

  • 模块化与解耦:将系统拆分为多个独立的模块,降低模块间的耦合度,提高系统的可维护性和可扩展性。
  • 文档化:编写详细的系统设计文档和接口文档,方便团队成员理解和维护系统。
  • 团队协作与沟通:建立高效的团队协作机制,确保团队成员之间的信息畅通和及时沟通。

48、aio 和 nio 它是跟什么相关的?是 Java 做的还是系统底层做的

NIO,即New I/O,是Java 1.4中引入的java.nio包,它提供了一套新的I/O抽象,如Channel、Selector和Buffer等,用于构建多路复用的、同步非阻塞的I/O程序。NIO是基于块的,它以块为基本单位处理数据,性能上比基于流的方式要好一些。同时,NIO提供了更接近操作系统底层高性能的数据操作方式。

而AIO,即Asynchronous I/O,是Java 1.7之后引入的包,作为NIO的升级版本,它提供了异步非阻塞的I/O操作方式。异步IO是基于事件和回调机制实现的,应用操作之后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

虽然这些I/O模型在Java中实现,但它们背后的原理和概念是与操作系统底层I/O操作紧密相关的。Java的I/O库是对操作系统提供的I/O功能的封装,使得Java程序员可以在不直接操作底层系统调用的情况下,使用更高级别的抽象进行I/O操作。因此,虽然这些I/O模型是在Java中实现的,但它们的应用和效果是与操作系统底层I/O操作紧密相关的。

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

50、为什么我们常用的是NIO而不是AIO?

因为我们一般是部署到linux系统上的,linux系统对nio的支持好一些

51、java里面线程分为哪两类

  • 用户线程(User Thread):这是最常见的线程类型,由用户程序创建和管理。用户线程通过start()方法启动,并可以异步执行特定的任务。在Java中,用户线程默认是非守护线程,即它们会一直运行,直到完成自己的任务或者因为某种原因(如异常)而终止。
  • 守护线程(Daemon Thread):守护线程主要用于在后台为其他线程(特别是用户线程)提供服务。与用户线程不同,守护线程在Java虚拟机(JVM)退出时会自动结束,不会阻止JVM的关闭。典型的守护线程包括垃圾回收线程。要将一个线程设置为守护线程,可以在调用start()方法之前调用线程的setDaemon(true)方法。

以下是一些守护线程在Java中的常见应用场景:

  • 垃圾回收:Java中的垃圾回收器就是一个守护线程。它会在程序运行过程中自动回收不再使用的内存,从而释放资源并减轻程序员的负担。当没有其他线程在运行时,垃圾回收器会自动执行垃圾回收操作。
  • 自动保存:在一些需要定时保存数据的场景中,可以使用守护线程来定时执行自动保存操作。例如,定时将缓存数据持久化到数据库,以防止意外情况导致数据丢失。这种机制可以在不影响主线程运行的情况下,确保数据的安全性和一致性。
  • 定时任务:守护线程也适用于执行定时任务,比如定时发送心跳包、定时更新缓存等。这些任务不需要实时响应,而是可以按照预定的时间间隔在后台执行。
  • 服务监控:守护线程还可以用于监控程序运行状态,比如监控CPU、内存的使用情况,以及网络连接是否正常等。这有助于及时发现潜在问题并进行处理,确保程序的稳定运行。

52、java内存模型 三大特性是什么

Java内存模型(Java Memory Model,JMM)是一种规范,定义了Java程序中各种变量的访问方式、存储方式和内存可见性等行为,以确保不同线程对共享变量的操作能够正确、可靠地进行。JMM关注的是Java程序中的内存访问规则和多线程并发操作的语义。

Java 内存模型(Java Memory Model,JMM)主要包括以下三大特性:

原子性(Atomicity):原子性指的是一个操作是不可中断的。即使在多线程的环境下,一个操作一旦开始,就一定会执行完毕,不会被其他线程的操作中断。在 Java 中,可以通过synchronized关键字或者使用java.util.concurrent.atomic包下的原子类来实现原子性。

可见性(Visibility):可见性指的是当一个线程修改了共享变量的值,其他线程能立即看到修改后的值。在多核处理器的系统中,每个线程在运行时都有自己的工作内存,而共享变量存储在主内存中。可见性确保了线程对共享变量的修改对其他线程是可见的。在 Java 中,可以通过volatile关键字或者通过synchronized和Lock来实现可见性。

有序性(Ordering):有序性指的是程序执行的顺序按照代码的先后顺序执行,即保证线程内串行语义的一致性。Java 内存模型通过 happens-before 原则来保证多线程执行时对共享变量的操作是有序的。具体来说,如果一个操作 A happens-before 另一个操作 B,那么操作 A 的结果对于操作 B 是可见的,并且操作 A 发生在操作 B 之前。

这三大特性构成了 Java 内存模型的核心,它们保证了在多线程环境下共享变量的正确性和一致性。

53、java内存模型和jvm区别是什么

Java内存模型是关于多线程并发编程的规范,而JVM(Java Virtual Machine)是Java程序的运行环境,它负责将Java字节码翻译成特定平台的机器码并执行。JVM包括了内存管理、垃圾回收、线程调度等方面的功能

54、线程状态有哪些,如何让线程中断

在Java中,线程状态主要有以下几种:

  1. 新建状态(NEW):当用new关键字创建一个线程对象后,该线程对象就处于新建状态。此时它已经有了相应的内存空间和其它资源,但是还没有开始执行。
  2. 就绪状态(RUNNABLE):当线程对象调用了start()方法后,该线程就进入就绪状态。此时,线程已经准备好运行,但是否真正执行取决于操作系统的调度。
  3. 运行状态(RUNNING):当线程获得CPU资源并执行任务时,它就处于运行状态。在任意时刻,只有一个线程处于运行状态,其他线程则在等待或竞争资源。
  4. 阻塞状态(BLOCKED):线程因为某种原因(如等待锁)放弃CPU使用权,暂时停止运行。当线程处于阻塞状态时,它不会占用任何CPU资源。
  5. 等待状态(WAITING):线程通过调用对象的wait()方法进入等待状态。此时,线程需要等待其他线程做出一些特定动作(如通知)。
  6. 超时等待状态(TIMED_WAITING):线程通过调用对象的sleep(long time)、wait(long time)或线程的join(long time)方法,或者是等待某个已触发的Thread.sleep、Object.wait、Thread.join等结构的超时时间到达,进入超时等待状态。
  7. 终止状态(TERMINATED):当线程因为run()方法执行完毕或者因为异常而退出,线程就处于终止状态。

要让线程中断,Java提供了几种方法:

  • 使用interrupt()方法:调用线程的interrupt()方法并不会直接停止线程,而是会在线程中设置一个中断状态。线程需要定期检查这个中断状态(通常通过
  • List item

Thread.currentThread().isInterrupted()或Thread.interrupted()方法),并根据需要做出响应。如果线程在sleep()、wait()或join()等阻塞状态下被中断,会抛出InterruptedException异常。
2. 使用stop()方法:虽然Java提供了stop()方法,但这个方法已经被废弃,不建议使用。因为它会立即停止线程,这可能导致线程中的数据不一致,或者线程未完成的清理工作得不到完成(如文件、数据库等的关闭)。
3. 使用退出标志:另一种方法是使用退出标志。在线程的run()方法中,你可以定期检查一个共享变量(即退出标志),如果该变量表示线程应该退出,则线程可以通过正常的方式(如完成当前循环或任务)来结束执行。

55、线程安全问题的本质是什么?JMM解决了什么问题

本质上来说线程安全问题就是多线程不能同时满足:可见性、原子性、有序性。而JMM提供了这些问题的解决方案

56、怎么知道synchronized锁在哪一个状态

使用Java监控和管理工具:

  • JConsole 和 VisualVM 是Java提供的两个图形化监控工具,它们可以显示线程的状态,包括哪些线程正在等待锁。这可以帮助你推断出哪些 synchronized 块或方法当前被锁定。
  • JStack 和 jcmd 线程 print 命令可以生成线程堆栈跟踪。通过分析这些堆栈跟踪,你可以找到哪些线程正在执行synchronized` 块或方法。

57、synchronized 方法块、普通方法、静态方法 分别锁的对象是什么

在这里插入图片描述

58、核心线人数和那个队列长度一般怎么样去配置?

计算公式:qps * tp99 + 系统抖动
一个普通的做法是进行压力测试,模拟不同的负载场景,然后根据应用程序的实际表现来调整参数。
实际上线后需要监控2-3天

如何具体配置取决于你的应用程序和硬件特性。通常,你需要基于以下几个因素来决定:

任务的性质:CPU密集型任务(计算密集)、IO密集型任务(等待I/O操作)或者两者的混合。
服务器的硬件资源:如处理器核心数、内存大小等。
系统的预期负载:即预计的并发任务数和任务产生的速率。
性能指标:比如,最大响应时间、吞吐量等。

在实际操作过程中,你可能需要对线程池的配置进行调整和优化,以达到最佳的性能。一个普通的做法是进行压力测试,模拟不同的负载场景,然后根据应用程序的实际表现来调整参数。
使用Java中的ThreadPoolExecutor时,你可以通过监视任务的等待时间、执行时间、队列长度以及线程池大小等指标来帮助决策。在测试期间,这些指标会指示现有配置的效能,并且可以用来判断是否需要调整核心线程数或队列长度。

59、多线程如何实现任务编排?

可以通过 CompletableFuture 实现
在这里插入图片描述

60、reentrantlock 如何实现公平和非公平锁的

具体实现都是在 reentrantlock 的NonfairSync 和 fairSync方法里面。重写了 aqs 的 tryAcquire 方法。

ReentrantLock 是 Java 中的一个可重入锁,它提供了公平和非公平两种锁的实现方式。理解这两种实现方式的关键在于理解它们如何决定哪个线程应该获得锁。

  • 非公平锁

在非公平锁的实现中,当锁被释放时,任何正在等待获取锁的线程都有机会立即获取锁,而不考虑它们等待锁的顺序。这种实现方式可能导致线程“插队”,即等待时间较长的线程可能会被等待时间较短的线程抢先获取锁。

非公平锁的实现通常具有更高的吞吐量,因为它减少了线程之间的切换和上下文切换的开销。但是,它可能导致某些线程长时间得不到执行,即出现饥饿现象。

  • 公平锁

在公平锁的实现中,线程按照它们请求锁的顺序来获取锁。也就是说,等待时间最长的线程会优先获取锁。这种实现方式保证了等待的线程不会因为其他线程的插队而长时间得不到执行。

公平锁的实现通常具有更好的可预测性,因为它遵循了一种先到先得的原则。但是,由于它需要在等待队列中维护线程的顺序,并可能需要更复杂的调度机制,因此其性能可能略低于非公平锁。

ReentrantLock 通过内部的同步队列(Sync Queue)来实现这两种锁的语义。当线程请求锁时,它会被添加到同步队列中。对于非公平锁,当一个线程释放锁时,它会直接尝试获取锁,而不是查看同步队列中是否有其他线程正在等待。而对于公平锁,当一个线程释放锁时,它会检查同步队列的头部是否有等待的线程,如果有,则将该线程从队列中移除并允许它获取锁。

61、链路追踪中的父子线程传递怎么实现(threadlocal)

当涉及到父子线程传递时,ThreadLocal默认情况下并不会自动将父线程的变量传递给子线程。这是因为ThreadLocal的设计初衷就是为了存储线程局部变量,这些变量的生命周期与线程的生命周期相同。

然而,你可以通过一些方法来模拟父子线程之间的ThreadLocal变量传递。以下是一些可能的方法:

1. 显式传递

在创建子线程时,你可以显式地从父线程的ThreadLocal中获取值,并将其作为参数传递给子线程。然后,子线程可以在其自己的ThreadLocal中设置这个值。

public class MyRunnable implements Runnable {  
    private final MyContext context;  
  
    public MyRunnable(MyContext context) {  
        this.context = context;  
    }  
  
    @Override  
    public void run() {  
        try (ThreadLocal<MyContext> threadLocal = new ThreadLocal<>()) {  
            threadLocal.set(context);  
            // ... 执行任务 ...  
        }  
    }  
}  
  
// 在父线程中  
MyContext parentContext = ...; // 从ThreadLocal中获取  
MyRunnable runnable = new MyRunnable(parentContext);  
new Thread(runnable).start();

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

2. 使用InheritableThreadLocal:

Java提供了一个InheritableThreadLocal类,它是ThreadLocal的一个子类。与ThreadLocal不同,InheritableThreadLocal允许父线程将其值传递给子线程。这是通过Java线程模型的inheritableThreadLocals字段实现的,该字段在创建新线程时会被复制到子线程中。

public class MyContextHolder {  
    private static final ThreadLocal<MyContext> contextHolder = new InheritableThreadLocal<>();  
  
    public static void setContext(MyContext context) {  
        contextHolder.set(context);  
    }  
  
    public static MyContext getContext() {  
        return contextHolder.get();  
    }  
  
    public static void clearContext() {  
        contextHolder.remove();  
    }  
}  
  
// 在父线程中  
MyContext parentContext = ...; // 创建或获取  
MyContextHolder.setContext(parentContext);  
  
// 创建并启动子线程  
new Thread(() -> {  
    MyContext childContext = MyContextHolder.getContext(); // 获取父线程的context  
    // ... 执行任务 ...  
}).start();

需要注意的是,虽然InheritableThreadLocal提供了父子线程之间的值传递,但它也可能导致一些意想不到的问题,尤其是在复杂的线程模型(如线程池)中。因此,在使用它时要格外小心,确保你了解其工作原理和潜在的影响。

3.使用上下文传递工具

除了上述方法外,你还可以考虑使用一些上下文传递工具或框架,如OpenTracing、Sleuth等。这些工具通常提供了更强大和灵活的上下文传递机制,可以更容易地集成到你的应用中。

62、线程死锁的条件是什么,如何解决

线程死锁是多线程编程中常见的问题,它指的是两个或更多个线程因为争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法向前推进。以下是线程死锁通常需要满足的四个条件:

  • 互斥条件:一个资源只能被一个线程使用。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程已获得的资源,在未完成之前不释放。
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

为了解决线程死锁问题,可以采取以下几种方法:

  • 避免嵌套锁:在使用多个锁的时候,避免使用嵌套锁,因为嵌套锁会增加死锁的风险。
  • 避免循环等待:在使用多个锁的时候,避免循环等待。如果出现循环等待的情况,可以采取破坏循环等待的方式,例如通过按照固定的顺序获取锁来避免死锁。
  • 设置超时时间:在使用锁的时候,设置超时时间。如果在超时时间内没有获取到锁,放弃锁并进行其他的处理。
  • 使用非阻塞算法:非阻塞算法会在没有锁的情况下执行操作,如果发现有其他线程正在使用资源,它会尝试重新执行操作,从而避免了死锁的风险。
  • 破坏死锁条件:可以通过破坏上述四个死锁条件中的任何一个来避免死锁。例如,通过让线程按照特定的顺序获取资源,可以破坏循环等待条件。

63、为什么从永久代变成元空间

永久代在Java 8及之前的版本中,是JVM中用于存储类信息的内存区域。然而,永久代存在一些问题:

  • 大小调整困难:永久代的大小在JVM启动时就需要指定,并且后续很难动态调整。这导致在应用程序运行时,如果加载的类过多,可能会引发OutOfMemoryError(OOM)错误。
  • 垃圾回收效率问题:永久代使用Java堆的垃圾回收器进行垃圾回收,但由于其存储的是类信息,垃圾回收的效率可能不如其他类型的内存区域。

相比之下,元空间在Java 8及之后的版本中引入,用于替代永久代。元空间在本地内存中分配,具有以下几个优势:

  • 动态扩展性:元空间的大小只受本地内存限制,可以根据应用程序的需要自动调整大小,从而减少了OOM的风险。
  • 垃圾回收优化:元空间使用专门的垃圾回收器进行垃圾回收,这种回收器针对类元数据的特点进行了优化,提高了垃圾回收的效率。
  • 更好的性能:由于元空间在本地内存中分配,与Java堆分开,因此可以减少堆内存的压力,提高应用程序的性能。

此外,StringTable(字符串常量池)的存放位置也从永久代转移到了堆中。这是因为永久代的回收频率相对较低,只有在Full GC时才会被回收。如果大量字符串被创建并放置在永久代中,可能会因为永久代空间不足而导致性能问题。将StringTable放到堆中,可以及时回收不再使用的字符串对象,从而避免空间不足的问题。

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值