跳至主要內容

(4)2019阿里天猫Java面试题


1 请自我介绍,说说自己擅长的技术?

以下是一个简洁而有效的自我介绍模板:

  • 基本信息:简历上也有基本信息,口述一遍当作开场,说一下毕业学校、所学专业、获奖经历,时间控制在半分钟。
  • 项目经历:按照简历上的内容,按时间由远及近说说开发过的项目,运用什么技术栈,做出什么成绩,有什么收获,时间控制在两分半钟
  • 技术专长:说说自己的特长,贡献过哪些开源项目,业余时间看什么书,是否写博客,要契合当前职位所需要的技术栈,时间控制在1分钟。
  • 职业规划:说一下的职业规划和中长期打算,表示自己稳定可靠不轻易跳槽,时间控制在半分钟

2 说说Treemap和HashMap的区别?

在Java语言中,TreeMap和HashMap是两种常用的Map实现,它们在内部实现、元素顺序、性能和适用场景等方面有明显的区别。

  • *内部实现

    • HashMap:HashMap基于哈希表实现,使用哈希函数将键映射到存储桶中。每个存储桶通常是一个链表或者更高效的红黑树(从JDK 8开始),以处理哈希冲突。
    • TreeMap:TreeMap基于红黑树实现,是一种自平衡的二叉搜索树。根据键的自然顺序或者提供的Comparator对键进行排序。
  • *元素顺序

    • HashMap:HashMap中的元素顺序是不确定的,与插入顺序无关。
    • TreeMap:TreeMap中的元素是按照键的自然顺序或者Comparator提供的顺序进行排序的。
  • 性能

    • HashMap:HashMap通常具有更好的性能,因为它使用了哈希函数,能够在常量时间内执行插入、删除和查找操作。
    • TreeMap:TreeMap的性能通常略低于HashMap,因为它使用红黑树作为底层数据结构,插入、删除和查找操作的时间复杂度为O(log n)。
  • 适用场景

    • HashMap:适用于大多数需要快速查找、插入和删除元素的场景,不需要关心元素的顺序。
    • TreeMap:适用于需要有序遍历键集合或按照键的顺序查找、删除元素的场景。
  • null键和null值

    • HashMap:允许一个null键和多个null值。
    • TreeMap:不允许null键,但允许多个null值。

因此,选择HashMap还是TreeMap取决于具体的需求。如果需要快速的查找、插入和删除操作,并且不关心元素的顺序,则HashMap是更好的选择。如果需要有序的键集合或者按照键的顺序进行操作,则应该选择TreeMap。

3 说说HashMap和ConcurrentHashMap的区别?

Java语言中的HashMap和ConcurrentHashMap是两种不同的Map实现,它们在多线程环境下的并发处理能力上有明显的区别。

  1. 线程安全性

    • HashMap:HashMap是非线程安全的。如果多个线程同时修改HashMap,可能会导致数据不一致或者抛出ConcurrentModificationException异常。
    • ConcurrentHashMap:ConcurrentHashMap是线程安全的。它通过分割存储空间(即将数据分成一段一段的存储区域),并对每一段区域进行加锁,从而实现了在多线程环境下的高效并发操作。
  2. 并发性能

    • HashMap:由于HashMap不提供线程安全性,因此在多线程环境下,需要手动使用同步机制(如使用Collections.synchronizedMap()包装)来保证线程安全,这可能会导致性能下降。
    • ConcurrentHashMap:ConcurrentHashMap针对并发环境进行了优化,可以在多线程环境下高效地进行读取和修改操作,性能通常比HashMap在并发情况下更好。
  3. 迭代器

    • HashMap:在HashMap进行迭代的过程中,如果其他线程修改了HashMap的结构(增加、删除元素),可能会抛出ConcurrentModificationException异常。
    • ConcurrentHashMap:ConcurrentHashMap的迭代器是弱一致的(Weakly Consistent),它不会抛出ConcurrentModificationException异常,但不保证迭代器会获取最新的修改。
  4. 锁策略

    • HashMap:HashMap在整个数据结构上使用一把锁(Fail-Fast机制),即在对HashMap进行修改的时候会锁住整个HashMap,因此在并发环境下性能较差。
    • ConcurrentHashMap:ConcurrentHashMap使用了分段锁(Segment Locks)或者CAS(Compare and Swap)等机制,只对需要修改的段进行锁定,提高了并发性能。
  5. 初始容量和负载因子

    • HashMap:在创建HashMap时可以指定初始容量和负载因子,但是需要手动管理扩容。
    • ConcurrentHashMap:ConcurrentHashMap允许动态地增加或减少容量,并且不需要手动处理扩容的问题。

总的来说,ConcurrentHashMap是为了在多线程环境下提供更好的并发性能而设计的,它是线程安全的、高效的,并且提供了弱一致的迭代器。而HashMap则是非线程安全的,需要在多线程环境下使用同步机制来保证线程安全。

4 说说一致性Hash算法?

一致性哈希算法(Consistent Hashing)是一种用于分布式系统中数据分片和负载均衡的算法。它主要解决了在增加或删除服务器节点时,如何尽可能地减少数据重新分布的问题。

在传统的哈希算法中,当服务器节点数量发生变化时,大部分的数据都需要重新映射到新的节点上,这会导致大量的数据迁移和缓存失效,影响系统的稳定性和性能。而一致性哈希算法则通过引入虚拟节点和环形哈希空间来解决这个问题。一致性哈希算法的基本思想如下:

  1. 将服务器节点和数据都映射到一个固定大小的环形哈希空间上,例如将服务器节点的哈希值映射到0到2^32-1的范围内。
  2. 将数据根据其哈希值映射到环形哈希空间上的某个位置,例如使用数据的哈希值作为其在环形空间上的位置。
  3. 当需要查找数据时,首先计算数据的哈希值,并将其映射到环形空间上。然后沿着环形空间顺时针方向找到离数据哈希值最近的服务器节点,将数据路由到该节点上。
  4. 当服务器节点数量发生变化时,只有与新增或删除的节点相邻的数据才需要重新映射到新的节点上,其他数据保持不变。这样可以尽可能减少数据迁移和缓存失效的影响。
  5. 为了平衡负载,可以在环形空间上引入虚拟节点,使得每个服务器节点在环形空间上对应多个位置。这样可以使数据分布更加均匀,提高负载均衡性。

一致性哈希算法的优点包括:

  • 数据分布均匀:一致性哈希算法将数据均匀地分布在环形空间上,可以有效地避免数据热点问题和负载不均衡。
  • 减少数据迁移:当服务器节点数量发生变化时,只有少量数据需要重新映射到新的节点上,可以减少数据迁移的成本。
  • 简单高效:一致性哈希算法简单易实现,并且在实际应用中性能表现良好。

总的来说,一致性哈希算法是一种高效的数据分布和负载均衡算法,广泛应用于分布式系统中的缓存、分布式存储和负载均衡器等场景。

5 GC算法和回收策略有哪些?

在Java虚拟机中,垃圾收集(Garbage Collection)是自动管理内存的重要机制。GC算法和回收策略是实现垃圾收集的关键组成部分,它们有多种不同的实现方式和策略。以下是常见的GC算法和回收策略:

  1. 标记-清除算法(Mark-Sweep)
    • 标记-清除算法是最基本的垃圾收集算法之一。它通过两个阶段实现:标记阶段用于标记出所有活动对象,清除阶段用于清除所有未标记的对象。这种算法会产生内存碎片,可能会导致内存分配效率降低。
  2. 复制算法(Copying)
    • 复制算法将内存分为两块,每次只使用其中的一块。当一块内存中的对象被使用完毕后,将所有存活的对象复制到另一块内存中,并且将原来的内存空间清空。这种算法避免了内存碎片的产生,但是需要耗费额外的内存空间。
  3. 标记-整理算法(Mark-Compact)
    • 标记-整理算法是在标记-清除算法的基础上进行改进的。它在标记阶段完成标记后,会将所有活动对象向一端移动,并且清除所有未标记的对象。这样可以保证内存中的活动对象是连续存放的,从而避免了内存碎片的产生。
  4. 分代算法(Generational)
    • 分代算法根据对象的生命周期将内存划分为多个代(Generation)。一般情况下,新创建的对象会被分配在新生代(Young Generation),而经过多次垃圾收集仍然存活的对象会被移到老年代(Old Generation)。针对不同代的对象使用不同的垃圾收集算法和回收策略,以提高垃圾收集的效率。

GC回收策略如下:

  1. Serial回收器

    • Serial回收器是单线程的垃圾收集器,在新生代使用复制算法,在老年代使用标记-整理算法。适用于单核CPU环境下的客户端应用。
  2. Parallel回收器

    • Parallel回收器是多线程的垃圾收集器,它在新生代使用复制算法,在老年代使用标记-整理算法。适用于多核CPU环境下的客户端应用。
  3. CMS回收器

    • CMS回收器是基于标记-清除算法的垃圾收集器,它使用多线程并发执行垃圾收集,以降低垃圾收集的停顿时间。适用于对停顿时间有较高要求的服务器端应用。
  4. G1回收器

    • G1回收器是一种面向服务器的垃圾收集器,它将堆内存划分为多个小块(Region),并且采用分代和复制算法的思想。G1回收器能够根据应用程序的特性动态调整堆内存的大小,以达到更好的垃圾收集效果。

这些GC算法和回收策略在不同的场景和应用中都有其适用性和优缺点,选择合适的垃圾收集器和调优参数是实现高性能和稳定性的关键。

6 怎么样主动的通知JVM进行垃圾回收?

在Java中,通常情况下并不建议手动触发垃圾回收,因为Java虚拟机(JVM)有自己的垃圾回收机制,可以根据当前系统的状态和需要自动执行垃圾回收。手动触发垃圾回收可能会导致不必要的性能损失和系统资源浪费。

尽管如此,如果确实有必要,可以通过调用System.gc()方法来建议JVM执行垃圾回收。但需要注意的是,调用System.gc()方法并不保证会立即执行垃圾回收,JVM可能会根据自身的策略和状态来决定是否执行垃圾回收,因此不能完全依赖手动触发来实现对内存的释放。

以下是使用System.gc()方法手动触发垃圾回收的示例代码:

public class GarbageCollectorExample {
    public static void main(String[] args) {
        // 创建一些对象
        Object obj1 = new Object();
        Object obj2 = new Object();

        // 手动触发垃圾回收
        System.gc();
    }
}

需要注意的是,即使调用了System.gc()方法,JVM也不一定会立即执行垃圾回收。垃圾回收器可能会根据当前系统的负载和其他因素来决定是否执行垃圾回收,因此不能保证调用System.gc()方法后立即释放内存。

7 说说Java的双亲委派模型?

Java的双亲委派模型是一种类加载机制,它在Java虚拟机中用于加载类并确保类的唯一性,同时提供了安全性和隔离性。

双亲委派模型的基本思想是:当一个类加载器(称为子加载器)需要加载某个类时,它首先委派给其父加载器(称为父亲加载器)去尝试加载该类。如果父加载器能够成功加载该类,则直接返回给子加载器;如果父加载器无法加载,则子加载器才会尝试加载该类。

具体来说,双亲委派模型的步骤如下:

  1. 当一个类加载器收到加载请求时,首先检查自己已经加载过的类是否包含该类。如果已经加载过,则直接返回已加载的类,不再重复加载。
  2. 如果自己没有加载过该类,则将加载请求委派给其父加载器去尝试加载。
  3. 父加载器收到加载请求后,也会按照同样的步骤进行处理:首先检查自己已经加载过的类,如果包含该类,则直接返回;否则,继续委派给自己的父加载器去尝试加载。
  4. 如果父加载器无法加载该类,则再次将加载请求返回给子加载器。
  5. 如果所有的父加载器都无法加载该类,则子加载器自己尝试加载该类。如果加载成功,则将该类返回给请求的类加载器;如果加载失败,则抛出ClassNotFoundException异常。

双亲委派模型的优点包括:

  • 类的唯一性:由于每个类加载器都会委派给父加载器去加载类,因此同一个类在不同的类加载器中只会被加载一次,保证了类的唯一性。
  • 安全性:双亲委派模型可以防止恶意代码通过自定义类加载器来覆盖Java核心类,从而提高了系统的安全性。
  • 隔离性:每个类加载器都有自己的命名空间,加载的类只对自己的命名空间可见,因此能够实现类的隔离性,防止类之间的冲突。

总的来说,Java的双亲委派模型是一种重要的类加载机制,通过委派链的方式实现了类的唯一性、安全性和隔离性,为Java应用的安全稳定运行提供了基础支持。

**8 线程池创建的几个核心构造参数是什么?

在Java中创建线程池时,可以使用ThreadPoolExecutor类,它提供了多个构造函数,其中包含了一些核心参数,用于配置线程池的行为。以下是几个重要的核心构造参数:

  1. corePoolSize(核心线程数)

    • 核心线程数指的是线程池中保持活动状态的线程数量,即使这些线程处于空闲状态,也不会被立即销毁。只有在工作队列中的任务数量超出了核心线程数,并且需要创建新的线程来处理任务时,才会创建新的线程。
    • 线程池中的线程数量不会超过核心线程数。
  2. maximumPoolSize(最大线程数)

    • 最大线程数是线程池中允许存在的最大线程数量。当工作队列中的任务数量超过了核心线程数,并且无法将任务加入工作队列时,会创建新的线程来处理任务,直到线程数量达到最大线程数为止。
    • 超过最大线程数的任务将会被拒绝执行,默认情况下会抛出RejectedExecutionException异常。
  3. keepAliveTime(线程空闲时间)

    • 线程空闲时间指的是当线程池中的线程数量大于核心线程数时,多余的空闲线程在被回收之前等待新任务的最长时间。
    • 当线程空闲时间超过指定的时间时,空闲线程将会被销毁,直到线程数量减少到核心线程数为止。
  4. unit(时间单位)

    • 时间单位用于指定keepAliveTime参数的时间单位,通常可以选择毫秒、秒、分钟等时间单位。
  5. workQueue(工作队列)

    • 工作队列用于保存待执行的任务。当线程池中的线程数量达到核心线程数,并且有新的任务需要执行时,新的任务会被放入工作队列中等待执行。常见的工作队列包括ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue等。

这些构造参数可以通过ThreadPoolExecutor的构造函数进行设置,也可以通过Executors工厂类提供的静态方法来创建具有默认参数的线程池。

**9 说说可重入锁ReentrantLock和Synchronized有什么区别?

可重入锁和 synchronized 关键字都是用于实现多线程并发访问控制的机制,它们的作用是确保线程间的互斥访问和临界区的同步执行,从而避免出现数据竞争和并发访问的问题。可重入锁是一种支持重复加锁的锁机制。简单来说,当一个线程持有锁时,可以多次重复获取这个锁,而不会产生死锁或者其他异常情况。这种机制能够帮助开发者编写更加灵活的代码,并且更容易实现对临界区的精确控制。

Java 中的 ReentrantLock 类是一个典型的可重入锁的实现。与 synchronized 相比,ReentrantLock 提供了更多的灵活性和功能,例如,它允许实现公平锁和非公平锁,可以中断等待锁的线程,可以尝试获取锁而不会一直阻塞等。

使用 ReentrantLock,典型的代码模式如下:

import java.util.concurrent.locks.ReentrantLock;

public class Example {
    private final ReentrantLock lock = new ReentrantLock();

    public void someMethod() {
        lock.lock();
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    }
}

synchronized 是 Java 中的关键字,用于实现对象的同步和线程的互斥。当一个线程获取了对象的 synchronized 锁时,其他线程必须等待该线程释放锁才能继续执行。synchronized 锁是隐式锁,它在进入同步代码块时自动获取锁,在退出时自动释放锁。

synchronized 可以用于方法级别的同步(使用在方法的定义上)和代码块级别的同步(使用在代码块内部)。它可以用于任意对象作为锁,也可以用于类级别的锁(即静态方法的同步)。

使用 synchronized 的示例:

public class Example {
    private final Object lock = new Object();

    public void someMethod() {
        synchronized (lock) {
            // 临界区代码
        }
    }
}

可重入锁和 synchronized 都是用于实现线程间的同步和互斥的机制,确保临界区的同步执行。ReentrantLock 提供了更多的灵活性和功能,但是使用相对更为复杂,而 synchronized 则是更为简单直接的同步方式,适用于大多数情况。选择使用哪种机制取决于具体的需求和场景。

10 CountDownLatch和Cylicbarrior的区别和使用场景是什么?

CountDownLatchCyclicBarrier 都是 Java 中用于控制多线程并发执行的同步工具,它们有一些相似之处,但也有明显的区别,适用于不同的场景。

CountDownLatch 是一种同步工具,它允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,计数器的初始值可以设定为任意整数,当计数器的值减为0时,等待的线程会被唤醒。

  • 特点

    • CountDownLatch 的计数器一旦被初始化,就不能重新设置或者增加值。
    • CountDownLatch 的计数器只能递减不能递增。
    • CountDownLatch 不可重用。
  • 使用场景

    • 主线程等待多个子线程完成某个任务后再继续执行。
    • 一个线程等待多个线程完成某个任务后再执行。
    • 多个线程等待某个共同事件的发生。

CyclicBarrier 是另一种多线程同步工具,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点。一旦所有线程都到达了屏障点,所有线程都会被释放继续执行。

  • 特点

    • CyclicBarrier 的计数器在每次屏障点被触发时会被重置。
    • CyclicBarrier 的计数器可以递增递减。
  • 使用场景

    • 一组线程需要等待彼此达到一个共同点,然后一起继续执行。
    • 拆分任务为多个子任务,并行处理,最后合并结果。
    • 多个线程相互等待,直到所有的线程都完成了各自的任务,然后再进行下一步操作。

这两者区别:

  1. 计数器行为

    • CountDownLatch 的计数器递减且不可重置。
    • CyclicBarrier 的计数器在每次屏障点被触发时会被重置。
  2. 使用场景

    • 如果需要一组线程等待另一组线程执行完毕后再执行,可以使用 CountDownLatch
    • 如果需要一组线程相互等待直到所有线程都到达某个共同点,然后一起继续执行,可以使用 CyclicBarrier

综上所述,CountDownLatch 适合用于一组线程等待另一组线程的完成,而 CyclicBarrier 适合用于一组线程相互等待直到所有线程都到达某个共同点。

11 Http和Https的区别是什么?

HTTP(超文本传输协议)和HTTPS(安全超文本传输协议)是用于在网络上传输数据的两种协议,它们之间有几个重要的区别:

  1. 安全性

    • HTTP:HTTP是一种明文传输协议,数据在传输过程中是以纯文本形式进行传输的,因此容易被窃听和篡改。不提供数据加密和身份验证机制,安全性较低。
    • HTTPS:HTTPS通过使用SSL/TLS协议对数据进行加密和身份验证,可以确保数据在传输过程中的机密性和完整性。通过在客户端和服务器之间建立加密通道,有效地防止了中间人攻击和数据窃取。
  2. 加密方式

    • HTTP:HTTP不提供数据加密功能,所有的数据都是以明文形式传输的。
    • HTTPS:HTTPS使用SSL/TLS协议对数据进行加密,可以采用对称加密、非对称加密和哈希算法等多种加密方式,确保数据的安全传输。
  3. 端口号

    • HTTP:HTTP默认使用端口号80进行通信。
    • HTTPS:HTTPS默认使用端口号443进行通信。
  4. 证书

    • HTTP:HTTP不需要使用证书进行身份验证,因为它是一种明文传输协议。
    • HTTPS:HTTPS需要使用SSL证书进行服务器身份验证,以确保客户端与服务器的通信是安全可信的。通常情况下,HTTPS网站会向客户端提供经过认证的SSL证书,以证明服务器的身份。
  5. 性能

    • HTTP:由于不需要进行加密和身份验证,HTTP的性能通常比HTTPS更高。
    • HTTPS:HTTPS需要进行加密和身份验证等操作,会对服务器和客户端的性能产生一定的影响,通常会比HTTP慢一些。

综上所述,HTTPS相对于HTTP具有更高的安全性,能够有效地保护数据在传输过程中的机密性和完整性,但同时也会增加一定的服务器和客户端的负担。因此,在需要保护用户隐私和数据安全的场景下,建议使用HTTPS协议。

上次编辑于: