JAVA面试部分——后端-线程后篇

3.12 如果在运行当中,遇到线程不够了,会以什么样的方式创建线程

线程池在运行过程中,如果遇到线程不够的情况,会根据线程池的类型和配置进行不同的处理:

  • 对于固定大小的线程池:如果线程因异常结束,会有一个新的线程来替代它。线程池的大小一旦达到最大值就会保持不变。

  • 对于可缓存的线程池:线程池的大小超过了任务所需要的线程,就会回收部分空闲的线程。当任务数增加时,此线程池又可以智能地添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

无论哪种类型的线程池,当需要执行新的任务但所有线程都在忙时,任务会被放入等待队列中,等待其他线程空闲后执行。如果等待队列也已满,且系统允许创建新线程,那么线程池会创建新的线程来处理该任务。如果系统不允许创建新线程,则根据拒绝策略来处理该任务。

3.13 Java多线程引发的问题?

Java多线程可能引发以下问题:

  • 内存资源耗尽:Java中每个线程都会占用一部分内存空间,当线程数过多时,会导致系统内存资源的消耗增加。如果系统内存无法满足所有线程所需的内存,则会引发OutOfMemoryError异常,在这种情况下,系统很可能会崩溃或死锁。

  • CPU资源利用率降低:过多的线程数会使CPU在调度线程时的负担增加。因为在任何时刻,CPU只有一个核心可以执行线程代码,当线程数过多时,CPU在不停地切换线程上下文,导致CPU利用率低下,从而降低系统性能。

  • 线程安全问题:访问共享的变量或资源,会有并发风险,这里的共享变量或资源指的是:对象的属性,静态变量,共享缓存,数据库等等。所有依赖时序的操作,即使每一步操作都是线程安全的,但是如果存在操作时序不对,比如操作的数据变量未初始化完成,依旧会产生并发问题。

  • 性能问题:从某种程度上来讲,多线程可以提高复杂的运算效率,但是一定程度上多线程可能会带来性能提交上下文切换。线程运行个数超过CPU核心数的时候,CPU就需要对线程进行调度,线程调度中就涉及线程切换,线程的切换的开销是很大的,CPU需要保存当前线程的运行场景,将当前线程的当前运行状态保存好,为载入新的运行线程做准备。这样来来回回其实是很耗费性能的。

为了避免这些问题,需要合理地配置和管理线程资源,确保线程的数量和优先级符合系统的需求和限制。同时,在编写涉及多线程的代码时,需要特别注意线程安全和同步问题,以避免出现竞态条件和数据不一致的问题。

3.14 并行与并发

并行是指多个任务在同一时间段内同时发生,这些任务可能由不同的处理器或者多核CPU来处理。并行通常可以显著提高程序的执行效率,因为它可以同时处理多个任务,而不是在一段时间内依次处理。在并行的情况下,不同的任务可以独立执行,不需要等待其他任务的处理。只有在多处理器或多核的机器上才能真正实现并行。

并发则是指多个任务在同一时间段内交替执行,从外部看来这些任务是同时进行的。并发通常通过在一个时间段内划分成若干个时间片段,然后在这些时间片段中依次执行每个任务来实现。这种方式可以减少等待时间,提高程序的响应速度。例如,在单处理器上运行的程序,通过合理的调度可以使得多个任务交替执行,实现并发的效果。但是,并发仍然需要等待某些任务的时间片段,因此其执行速度仍然受到一定的限制。

简而言之,并行是多个任务同时发生,并发是多个任务交替发生。并行更多地涉及到硬件的并行处理能力,而并发更多地涉及到软件的调度和执行策略。

3.15 多线程访问多个接口的时候怎么保证效率,比如访问A接口2s,B接口3s,如何优化?

在多线程访问A接口和B接口的情况下,可以采取以下优化措施:

  • 使用异步访问:对于需要等待较长时间的接口访问,例如B接口需要等待3秒,可以使用异步访问方式。这样主线程不会被长时间阻塞,可以继续执行其他任务,从而提高整体效率。

  • 合并接口访问:如果A接口和B接口之间存在依赖关系,可以将它们合并在一起进行访问。例如,可以先访问A接口,然后紧接着访问B接口,从而减少总的等待时间。

  • 使用线程池:通过线程池提供的一组线程来处理多个接口的访问请求。线程池可以重复利用已创建的线程,减少创建和销毁线程的开销,提高效率。

  • 优化网络连接:对于网络连接的优化,可以采取一些措施,如使用长连接、减少网络跳转等。这样可以减少网络通信的开销和时间,提高效率。

  • 缓存技术:对于频繁访问的接口,可以使用缓存技术。将已经获取的数据存储在缓存中,避免重复请求相同的接口,从而减少访问时间和网络开销。

综上所述,通过异步访问、合并接口访问、使用线程池、优化网络连接和采用缓存技术等措施,可以优化多线程访问A接口和B接口时的效率。具体优化方案需要根据实际情况进行选择和调整。

3.16 多线程去做io操作,Copy文件如何实现?

在多线程环境下进行文件复制操作可以提高效率,因为可以同时处理多个文件。下面是一种可能的实现方式:

  • 创建一个线程池,线程池的大小根据系统的硬件资源和文件数量来决定。

  • 将需要复制的文件分成多个批次,每个批次包含一定数量的文件。

  • 对于每个批次,向线程池提交一个任务,该任务负责复制该批次的文件。

  • 在每个任务中,使用操作系统提供的文件复制函数(如Java中的File.copy或Java的nio包中的FileChannel.transferTo方法)来复制文件。

  • 在复制过程中,可以设置线程的优先级为低,以避免复制操作占用过多资源导致系统响应缓慢。

  • 等待所有任务完成,完成后的文件可以通过某种方式进行合并,例如使用文件锁或者合并工具。

需要注意的是,在多线程复制文件时,需要确保文件的完整性和一致性。例如,如果一个文件正在被其他程序修改,可能会导致复制操作失败或者复制的文件不完整。因此,在复制前应该检查文件的完整性和一致性,或者等待文件被修改完成后再次尝试复制。

另外,还需要注意文件名冲突的问题。如果多个线程同时复制同一个目录下的文件,可能会出现文件名冲突的情况。可以通过重命名文件或者使用不同的目录来避免这种情况。

总之,多线程复制文件可以提高效率,但需要注意文件的完整性和一致性,以及避免文件名冲突的问题。

3.17 如何杀掉线程或进程?

在程序中终止线程或进程是一个需要谨慎处理的问题,因为如果处理不当,可能会导致程序崩溃或出现其他问题。以下是几种终止线程或进程的最佳方式:

  • 线程同步:使用各种线程同步机制,如信号量、事件等,来控制线程的执行。通过等待或通知线程继续执行,可以安全地终止线程。

  • 异常处理:在线程或进程中抛出异常,导致线程或进程终止。但是,这种方式可能导致程序崩溃或出现其他问题,因此不推荐使用。

  • 正常结束:通过在线程或进程的代码中添加退出条件,使其在满足条件时自动结束执行。这种方式是最安全和最推荐的方式。

  • 操作系统命令:在操作系统级别使用命令来终止线程或进程。例如,在Linux系统中,可以使用kill命令来终止进程。但是,这种方式可能会导致程序崩溃或出现其他问题,因此不推荐使用。

总之,最佳的终止线程或进程的方式是使用线程同步机制和正常结束执行。如果必须使用异常处理或操作系统命令来终止线程或进程,应该谨慎处理,以避免出现程序崩溃或其他问题。

3.18 AQS有了解吗?

Java中的AQS(AbstractQueuedSynchronizer)是一个用于实现并发同步的工具类,它提供了一种实现同步器的框架和实现方式。AQS的核心思想是利用一个先进先出(FIFO)的双向队列来管理线程的竞争和等待。它可以用于实现诸如ReentrantLock、Semaphore、CountDownLatch等同步工具类。

以下是一些AQS的应用:

  • ReentrantLock:ReentrantLock是一个可重入的互斥锁,它使用AQS来实现锁的功能。在ReentrantLock的实现中,通过继承AQS类并实现其抽象方法,利用state和exclusiveOwnerThread两个状态变量来实现加锁和解锁操作。

  • CountDownLatch:CountDownLatch是一个等待其他线程完成的操作,它使用AQS来实现等待操作。CountDownLatch内部维护了一个计数器,当计数器为0时,await()方法返回。而countDown()方法则用来减少计数器的值。

  • Semaphore:Semaphore是一个计数信号量,它使用AQS来实现信号量的功能。如果计数器为0,acquire()方法会阻塞,直到其他线程释放许可证。

除了上述应用之外,AQS还可以用于实现其他类型的同步工具类,如StampedLock等。总之,AQS是Java并发编程中的一个重要组成部分,它提供了一种灵活、高效的方式来实现并发同步。

3.19 说一下 synchroinzed 锁膨胀?

"synchronized"是Java中的一个关键字,用于实现同步。它提供了一种互斥的机制,确保同一时刻只有一个线程可以执行某个方法或代码块。然而,在实际应用中,由于一些情况可能会导致synchronized锁膨胀,进而影响程序的性能。

锁膨胀是指随着系统并发量的增加,锁的持有时间变长,锁的竞争加剧,导致需要更多的内存空间和CPU资源。在Java中,synchronized锁的膨胀通常表现为对象头中的Mark Word的膨胀。

当一个线程持有锁时,该锁会占用一个Mark Word,其中包含了一些元信息,如锁的持有状态、线程持有锁的时间等。随着并发量的增加,线程持有锁的时间变长,Mark Word中的元信息也会不断增加,从而导致Mark Word的膨胀。

锁膨胀会对程序的性能产生负面影响,因为它会增加内存占用和CPU的开销。当锁竞争激烈时,线程需要频繁地获取和释放锁,导致CPU的上下文切换和内存访问的开销增加,进而影响程序的性能。

为了避免锁膨胀对程序性能的影响,可以考虑使用其他并发控制机制,如使用CAS操作实现无锁算法、使用读写锁等。此外,也可以通过合理地设计程序的并发模型、减少锁的持有时间、避免锁的过度使用等方式来降低锁竞争和锁膨胀的风险。

3.20 锁的一些方法及使用

在Java中,常用的锁机制有synchronized和Lock接口。它们提供了一些方法和使用方式,可以用于实现互斥和并发控制。

  • synchronized:

    1. 使用方式:在方法或代码块前加上synchronized关键字即可。

    2. 实现方式:通过对象头中的Mark Word标识锁的持有状态,如果Mark Word的值为0,表示未加锁;如果Mark Word的值不为0,表示持有锁,其他线程需要等待该线程释放锁。

    3. 优点:自动获取和释放锁,使用简单;支持多个监视器(monitor)锁,允许多个线程同时持有锁。

    4. 缺点:不支持可重入性,即一个线程不能重复获取同一个锁;性能较差,因为需要使用对象头中的Mark Word来标识锁的持有状态。

  • Lock接口:

    1. 使用方式:创建一个Lock接口的实现类(如ReentrantLock),然后使用该实现类来加锁和解锁。

    2. 实现方式:通过实现Lock接口中的lock()和unlock()方法来实现加锁和解锁操作。

    3. 优点:支持可重入性,即一个线程可以多次获取同一个锁;性能较好,因为不需要使用对象头中的Mark Word来标识锁的持有状态。

    4. 缺点:需要手动获取和释放锁,使用较为繁琐;不支持多个监视器(monitor)锁,只能有一个线程持有锁。

除了synchronized和Lock接口之外,Java还提供了一些其他的锁机制,如读写锁(ReadWriteLock)、信号量(Semaphore)等。它们各有优缺点,可以根据具体的场景选择合适的锁机制。

3.21 什么是函数式接口,结构上有什么特点,能声明其他东西吗,默认方法有什么?

函数式接口是Java中的一种接口,它只包含一个抽象方法。在Java 8及以后的版本中,函数式接口被用于支持Lambda表达式和函数式编程。

函数式接口在结构上的特点如下:

  1. 只包含一个抽象方法。

  2. 可以包含默认方法和静态方法。

  3. 使用@FunctionalInterface注解进行标识,以确保它符合函数式接口的规范。

除了抽象方法之外,函数式接口还可以声明默认方法和静态方法。默认方法允许在接口中提供方法的默认实现,可以被实现接口的类选择性重写。静态方法则允许在接口中定义与接口本身相关的一些工具方法,类似于工具类的功能。

函数式接口的抽象方法可以用Lambda表达式或方法引用来实现。Lambda表达式是一种简洁的匿名函数写法,可以用来实现函数式接口的抽象方法。方法引用则是指引用现有方法,并传递给其他函数或对象。

默认方法允许在接口中提供默认实现,可以被实现接口的类选择性重写。默认方法主要用于在不影响现有代码的情况下添加新的功能,或者为旧版接口提供新的实现方式。

总之,函数式接口是Java中用于支持函数式编程的一种特殊接口,它只包含一个抽象方法,但可以包含默认方法和静态方法。函数式接口的抽象方法可以用Lambda表达式或方法引用来实现,而默认方法则允许在接口中提供默认实现。

3.22 什么情况会导致内存泄露

在Java中,以下是一些可能导致内存泄露的情况:

  1. 静态变量:如果静态变量引用了一个对象,而这个对象没有被其他变量引用,那么这个对象将无法被垃圾回收器回收,从而导致内存泄露。

  2. 监听器:如果一个对象持有监听器,但该对象本身被垃圾回收器回收,而监听器没有被正确取消,那么就会造成内存泄露。

  3. 集合类:如果集合类中存储了大量的对象,而这些对象没有被正确释放,那么就会造成内存泄露。

  4. 缓存:如果缓存中存储了大量的对象,而这些对象没有被正确释放,那么就会造成内存泄露。

  5. 未关闭的资源:如果程序中打开了一些资源(如数据库连接、文件流等),但未及时关闭,就会造成内存泄露。

为了避免内存泄露,程序员需要正确使用Java的内存管理机制,如使用弱引用、及时关闭资源、正确处理监听器等。同时,也可以使用一些工具来检测和修复内存泄露,如Java的内存分析工具(Memory Analyzer Tool,MAT)等。

你可能感兴趣的:(#,后端,java,面试,开发语言)