[持续更新]计算机经典面试题基础篇Day2

[通用]计算机经典面试题基础篇Day2

1、单例模式是什么,线程安全吗

单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。通过使用单例模式,可以避免多次创建相同的对象,节省内存资源,同时也可以确保对象的一致性。

在Java中,常见的实现单例模式的方式是使用懒汉式和饿汉式。

懒汉式:在首次使用时才创建实例。懒汉式的线程安全性取决于具体的实现方式。如果使用简单的懒汉式实现,在多线程环境下是不安全的,可能会导致多个线程同时创建多个实例。可以通过加锁(synchronized)或者使用双重检查锁(double-checked locking)来实现线程安全的懒汉式。

public class LazySingleton {
     
     
   private static LazySingleton instance;
   
   private LazySingleton() {
     
     
       // 私有化构造函数
   }
   
   public static synchronized LazySingleton getInstance() {
     
     
       if (instance == null) {
     
     
           instance = new LazySingleton();
       }
       return instance;
   }
}

饿汉式:在类加载时就创建实例。饿汉式是线程安全的,因为实例在类加载时就被创建,保证了全局唯一性。但由于实例的创建是提前进行的,可能会导致资源的浪费。

public class EagerSingleton {
     
     
   private static final EagerSingleton instance = new EagerSingleton();
   
   private EagerSingleton() {
     
     
       // 私有化构造函数
   }
   
   public static EagerSingleton getInstance() {
     
     
       return instance;
   }
}

2、Vector是什么,线程安全吗

Vector 是 Java 中的一种同步容器类,它实现了动态数组的功能。它与 ArrayList 类似,但是具有线程安全的特性。

线程安全是指在多线程环境下,对共享数据的访问和操作不会出现冲突或产生不一致的结果。对于 Vector 来说,它提供了一些同步方法,如 add()、remove() 和 get() 等,在多线程环境中可以确保对 Vector 的操作是线程安全的。

然而,尽管 Vector 的操作是线程安全的,但是在高并发的情况下,使用 Vector 还是可能会导致性能问题。因为 Vector 在进行操作时,会对整个集合进行加锁,这样其它线程就无法同时操作 Vector,导致效率下降。

所以,如果只需要在单线程环境下使用动态数组,建议使用 ArrayList。如果需要在多线程环境下使用,可以考虑使用 Vector 或者使用其他线程安全的容器类,如 CopyOnWriteArrayList 或 ConcurrentHashMap。

3、请你说说synchronized和lock的区别

synchronized 和 lock 都是 Java 中用于实现线程同步的机制。

  • synchronized 是关键字,可以直接应用于方法或代码块中,自动获取和释放锁。
  • lock 是接口,需要手动调用 lock() 和 unlock() 方法来获取和释放锁。

区别:

  • 锁的获取和释放方式:synchronized 自动获取和释放锁,lock 需要手动调用对应方法。
  • 可重入性:synchronized 和 lock 都是可重入的,但 lock 需要手动释放相应次数的锁。
  • 等待可中断:synchronized 无法中断等待,lock 提供可中断获取锁的方式。
  • 条件变量:synchronized 通过 wait()、notify() 和 notifyAll() 实现条件变量,lock 使用 Condition 接口实现更灵活的线程等待和唤醒机制。

总的来说,synchronized 简单易用,适合简单的线程同步,而 lock 提供更多功能和灵活性,适用于复杂的线程同步。在性能方面,synchronized 对短时间同步操作更高效,lock 对长时间同步更有优势。

4、请说一下什么是堆内内存,是怎么分配的

堆内存的分配是由 Java 虚拟机负责的。当我们创建一个对象时,Java 虚拟机会在堆内存中分配一块连续的内存空间来存储该对象的实例变量和其他相关信息。堆内存的大小可以通过启动参数来指定,或者根据系统的物理内存大小进行自动调整。

在堆内存中,对象是通过垃圾回收机制来进行管理和释放的。当一个对象不再被引用时,垃圾回收器会自动标记并回收该对象所占用的内存空间,以便后续的对象可以继续在该空间中分配。

堆内存的分配方式是动态的,在运行时根据程序的需求进行分配和释放。由于堆内存的分配和释放是比较耗时的操作,所以垃圾回收机制的性能和算法对于程序的性能和内存的使用效率都有很大的影响。

要注意的是,堆内存是多线程共享的,多个线程可以同时分配和访问堆内存中的对象。因此,在多线程编程中,需要注意对共享对象的访问同步和线程安全性的问题。

5、请说一下什么事对外内存,是怎么分配的

对外内存(Off-Heap Memory)是指在 Java 中,通过使用非堆内存来存储数据的一种方式。与堆内存不同,对外内存并不受 Java 垃圾回收机制的管理。

对外内存的分配和释放是由程序员手动控制的,而不是由 Java 虚拟机自动管理。一般情况下,对外内存的分配和释放是通过调用本地方法或操作系统提供的底层接口来完成的。

常见的使用对外内存的场景包括:

  1. 需要处理大量的数据,如大型数组、图像数据等,这些数据无法完全放入堆内存中。
  2. 需要使用常驻内存的数据结构,如缓存、数据库连接池等。
  3. 需要与底层系统进行交互,如直接操作文件、网络数据传输等。
  4. 需要提高性能和降低垃圾回收的影响,对外内存的分配和释放比堆内存更高效。

6、说一下进程和线程的区别

进程是程序的一个实例,拥有独立的内存空间和系统资源,线程是进程的执行单元,共享相同的资源。

  • 资源占用:进程占用独立资源,线程共享资源。
  • 切换成本:进程切换成本高,线程切换成本低。
  • 通信和同步:进程间通信需要特定机制,线程间可直接共享内存通信和同步。
  • 独立性:进程相互隔离,线程共享资源。
  • 容错性:进程崩溃不影响其他进程,线程崩溃可能导致整个进程崩溃。

7、说一下对多线程的理解

多线程是指在一个程序中同时运行多个线程,在多核处理器上并行执行,提高程序的并发性和效率。

多线程的理解可以从以下几个角度来考虑:

  1. 并发性和响应性:多线程可以使程序同时执行多个任务,提高程序的并发性,增加系统的吞吐量。同时,多线程可以提高程序的响应性,通过将耗时的操作放在后台线程中执行,使主线程能够快速响应用户的操作。
  2. 资源共享与同步:多线程共享同一进程的资源,如内存、文件、网络连接等。但同时也需要考虑线程之间的同步问题,确保对共享资源的访问是安全的,避免竞态条件等问题的发生。
  3. 并发控制与线程安全:多线程编程需要处理并发控制问题,如互斥锁、信号量、条件变量等,以确保线程的安全执行。线程安全是指多个线程访问共享资源时不会产生不正确的结果。
  4. 调度和优先级:多线程通过操作系统的调度机制在多核处理器上进行分配和调度。线程的优先级可以指定,高优先级的线程可能会更早地得到执行机会,但并不能保证绝对的顺序。
  5. 死锁与活锁:多线程编程中存在死锁和活锁的风险,死锁是指多个线程相互等待对方释放资源而无法继续执行,而活锁则是指线程虽然不会被阻塞,但由于某种逻辑问题导致无法正常执行。

8、说一下对线程池的理解

线程池是一种线程管理的机制,它可以提前创建一定数量的线程,并将任务提交给线程池来执行。线程池维护着一组可重用的线程,可以有效地管理和控制线程的数量,提高系统的性能和稳定性。

对线程池的理解可以从以下几个方面考虑:

  1. 线程复用:线程池在初始化时会创建一定数量的线程,并将其维护在池中。任务提交给线程池后,线程池会选择空闲的线程来执行任务,任务执行完毕后线程并不会被销毁,而是继续保留在池中,以供后续任务使用,从而避免了频繁地创建和销毁线程的开销。
  2. 线程管理和控制:线程池可以通过设置参数来控制线程的数量、线程的优先级、线程的闲置时间等。可以根据系统的负载情况动态地调整线程池的大小和配置,以适应不同的应用场景和资源需求。
  3. 任务排队和调度:线程池会维护一个任务队列,将提交给线程池的任务按顺序排队,并通过线程调度算法选择合适的线程来执行任务。任务队列可以避免任务过多导致系统资源耗尽,同时还可以实现任务的优先级调度、任务的拒绝策略等。
  4. 异常处理和监控:线程池可以对线程的异常进行处理,避免异常的传递导致整个系统崩溃。同时,线程池还可以提供监控和统计信息,如线程池的活动线程数、完成任务数、任务队列长度等,用于监控线程池的运行状态和性能指标。

9、java提供了那些线程池

  1. FixedThreadPool:固定大小的线程池,创建时指定线程数量,线程池中的线程数量始终保持不变。适用于需要控制并发线程数的场景。
  2. CachedThreadPool:可缓存的线程池,线程池中的线程数量根据需要自动调整,如果有空闲线程则复用,没有空闲线程则创建新线程。适用于执行大量短期异步任务的场景。
  3. ScheduledThreadPool:定时任务线程池,用于在给定的延迟后或定时执行任务。适用于需要定时执行任务的场景。
  4. SingleThreadExecutor:单线程化的线程池,只有一个工作线程执行任务,保证任务按照指定的顺序执行。适用于需要按顺序执行任务的场景。
  5. WorkStealingPool:工作窃取线程池,每个线程维护自己的任务队列,空闲线程会从其他线程的队列中窃取任务执行,以提高并行度。适用于执行大量相互独立的任务的场景。

这些线程池都实现了ExecutorService接口,可以通过ThreadPoolExecutor类进行更加灵活的配置和自定义线程池的行为。通过使用Java提供的线程池,可以避免手动创建和管理线程,提高系统的性能和可维护性。

10、说一下对hashmap的理解

ashMap是Java中的一种数据结构,它基于哈希表实现,用于存储键值对(Key-Value)的映射关系。HashMap提供了快速的查找、插入和删除操作,是常用的集合类之一。

对HashMap的理解可以从以下几个方面考虑:

  1. 存储结构:HashMap内部使用数组和链表(或红黑树)实现。数组用于存储桶(bucket),每个桶存储一条链表(或红黑树),链表(或红黑树)用于解决哈希冲突,即多个键映射到同一个桶的情况。
  2. 键值对映射:HashMap通过哈希函数将键映射为数组的索引位置,从而实现快速的查找操作。每个键值对存储在桶中的链表(或红黑树)中,通过键的哈希值来定位所在的桶,然后通过比较键的equals方法来找到对应的值。
  3. 哈希冲突:由于不同的键可能映射到同一个桶,因此HashMap需要解决哈希冲突的问题。当链表(或红黑树)中的元素过多时,链表会转换为红黑树,以提高查找的效率。
  4. 线程不安全:HashMap是非线程安全的,多个线程同时进行读写操作可能导致数据不一致的问题。如果需要在多线程环境下使用,可以使用ConcurrentHashMap或通过同步机制进行外部同步。
  5. 效率与容量:HashMap的性能受到初始容量和负载因子的影响。初始容量是哈希表在创建时的桶的数量,负载因子是哈希表在自动扩容之前可以达到的填充比例。过高的负载因子会导致哈希冲突的增加,而过低的负载因子会导致存储空间的浪费。

简而言之,HashMap是一种用于存储键值对的数据结构,通过哈希函数将键映射为数组的索引位置,以实现快速的查找操作。它提供了高效的插入、删除和查找操作,但需要注意线程安全性和合理的容量设置。

猜你喜欢

转载自blog.csdn.net/godnightshao/article/details/132741030