Android第三次面试(Java基础)

devtools/2025/3/20 2:35:25/

面试题一:在 Android 里,Array 和 ArrayList 区别?

  • 定义与大小:数组声明时要指定大小,之后固定;ArrayList 动态,无需提前定大小。
  • 性能:二者访问元素快,时间复杂度 O(1);数组插入删除繁琐,ArrayList 尾部添加快,其他位置操作慢。
  • 数据类型:数组能存基本类型和对象,ArrayList 只能存对象,存基本类型需用包装类。
  • 方法功能:数组自身方法少,靠 Arrays 类;ArrayList 自带众多实用方法。

扩展追问:扩容因子

  数组无自动扩容机制。创建时长度固定,若要扩容,需手动新建更大数组,再将原数组元素复制过去。

  ArrayList

  • 初始容量:未指定时默认初始容量为 10。
  • 扩容规则:添加元素使数量达容量上限时会自动扩容。新容量约为原容量 1.5 倍(oldCapacity + (oldCapacity >> 1))。若新容量小于所需最小容量,就以最小容量为准;若超数组最大容量,会特殊处理。扩容通过 Arrays.copyOf 复制元素到新数组。
java">private void grow(int minCapacity) {// 获取旧容量int oldCapacity = elementData.length;// 计算新容量,大约是原容量的 1.5 倍int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果新容量小于所需的最小容量,则将新容量设置为最小容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果新容量大于数组的最大容量,则调用 hugeCapacity 方法处理if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 调用 Arrays.copyOf 方法将原数组元素复制到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}

在 grow 方法中,首先计算新容量,大约是原容量的 1.5 倍(oldCapacity + (oldCapacity >> 1))。然后检查新容量是否满足最小容量要求,如果不满足,则将新容量设置为最小容量。最后,使用 Arrays.copyOf 方法将原数组元素复制到新数组中。

  综上所述,数组没有自动扩容机制,需要手动处理;而 ArrayList 有自动扩容机制,扩容后的新容量大约是原容量的 1.5 倍。

  面试二:synchronized锁对类,对象,代码块的作用

对类加锁(同步静态方法)

当使用 synchronized 修饰静态方法时,相当于对类加锁,锁对象是该类的 Class 对象。同一时间只有一个线程能够获取该类的 Class 对象的锁并执行该静态方法,其他线程必须等待锁释放。

java">public class ClassLockExample {// 同步静态方法public static synchronized void staticMethod() {try {System.out.println(Thread.currentThread().getName() + " 进入静态方法");Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + " 离开静态方法");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 创建两个线程调用静态方法Thread thread1 = new Thread(ClassLockExample::staticMethod, "线程1");Thread thread2 = new Thread(ClassLockExample::staticMethod, "线程2");thread1.start();thread2.start();}
}
作用分析
  • 在上述代码中,staticMethod 是一个同步静态方法。由于静态方法属于类,而不是类的实例,所以 synchronized 修饰静态方法时,锁的是类的 Class 对象。
  • 当 线程1 进入 staticMethod 方法时,它会获取 ClassLockExample.class 对象的锁,此时 线程2 必须等待 线程1 释放锁后才能进入该方法,从而保证了同一时间只有一个线程能执行该静态方法。

对对象加锁(同步实例方法)

当使用 synchronized 修饰实例方法时,相当于对对象加锁,锁对象是调用该方法的实例对象。同一时间只有一个线程能够获取该实例对象的锁并执行该实例方法,其他线程必须等待锁释放。

java">public class ObjectLockExample {// 同步实例方法public synchronized void instanceMethod() {try {System.out.println(Thread.currentThread().getName() + " 进入实例方法");Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + " 离开实例方法");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {ObjectLockExample example = new ObjectLockExample();// 创建两个线程调用实例方法Thread thread1 = new Thread(example::instanceMethod, "线程1");Thread thread2 = new Thread(example::instanceMethod, "线程2");thread1.start();thread2.start();}
}
作用分析
  • 在上述代码中,instanceMethod 是一个同步实例方法。synchronized 修饰实例方法时,锁的是调用该方法的实例对象,即 example 对象。
  • 当 线程1 进入 instanceMethod 方法时,它会获取 example 对象的锁,此时 线程2 必须等待 线程1 释放锁后才能进入该方法,从而保证了同一时间只有一个线程能执行该实例方法。

对代码块加锁

使用 synchronized 修饰代码块时,需要显式指定一个锁对象。同一时间只有一个线程能够获取该锁对象的锁并执行代码块中的代码,其他线程必须等待锁释放。

java">public class BlockLockExample {private final Object lock = new Object();public void blockMethod() {// 同步代码块synchronized (lock) {try {System.out.println(Thread.currentThread().getName() + " 进入同步代码块");Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + " 离开同步代码块");} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {BlockLockExample example = new BlockLockExample();// 创建两个线程调用包含同步代码块的方法Thread thread1 = new Thread(example::blockMethod, "线程1");Thread thread2 = new Thread(example::blockMethod, "线程2");thread1.start();thread2.start();}
}
作用分析
  • 在上述代码中,synchronized 代码块指定了 lock 对象作为锁。当 线程1 进入同步代码块时,它会获取 lock 对象的锁,此时 线程2 必须等待 线程1 释放锁后才能进入该代码块,从而保证了同一时间只有一个线程能执行该代码块中的代码。
  • 使用同步代码块的好处是可以更细粒度地控制同步范围,只对需要同步的代码部分加锁,减少锁的持有时间,提高性能。

总结

  • 对类加锁:通过同步静态方法实现,锁的是类的 Class 对象,确保同一时间只有一个线程能执行该类的静态同步方法,适用于控制对类级别的共享资源的访问。
  • 对对象加锁:通过同步实例方法实现,锁的是调用该方法的实例对象,确保同一时间只有一个线程能执行该实例的同步实例方法,适用于控制对实例级别的共享资源的访问。
  • 对代码块加锁:通过同步代码块实现,需要显式指定锁对象,确保同一时间只有一个线程能执行该代码块中的代码,适用于更细粒度的同步控制,减少锁的持有时间,提高性能。

 扩展追问:

  ReentrantLock 是什么?

ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个类,用于实现线程同步,它是一种可重入的互斥锁,在多线程编程中发挥着重要作用,下面从多个方面详细介绍它:

基本概念

  • 可重入性:“可重入” 意味着同一个线程可以多次获取同一把锁而不会造成死锁。当线程第一次获取锁时,锁的持有计数变为 1,若该线程再次获取这把锁,计数会相应增加,每次释放锁时计数减 1,只有当计数为 0 时,锁才真正被释放,其他线程才能获取该锁。
  • 互斥性:同一时刻,ReentrantLock 只能被一个线程持有,这保证了对共享资源的独占访问,避免多个线程同时修改共享资源导致的数据不一致问题。

基本使用

ReentrantLock 的使用通常包含获取锁、执行同步代码和释放锁这几个步骤,以下是一个简单示例:

java">import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();private int sharedResource = 0;public void increment() {// 获取锁lock.lock();try {// 执行同步操作sharedResource++;System.out.println(Thread.currentThread().getName() + " 执行递增操作后,共享资源的值为: " + sharedResource);} finally {// 释放锁,确保在发生异常时也能释放锁lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();// 创建两个线程进行操作Thread thread1 = new Thread(example::increment, "线程1");Thread thread2 = new Thread(example::increment, "线程2");thread1.start();thread2.start();}
}

在上述代码中,increment 方法使用 ReentrantLock 来保证线程安全。lock.lock() 用于获取锁,lock.unlock() 用于释放锁,为了防止在执行同步代码时发生异常导致锁无法释放,将 lock.unlock() 放在 finally 块中。

与 synchronized 的比较

灵活性
  • ReentrantLock:提供了更多的灵活性。例如,它可以使用 tryLock() 方法尝试获取锁,如果锁不可用,线程可以选择不阻塞,继续执行其他操作;还可以使用 lockInterruptibly() 方法允许线程在等待锁的过程中被中断。
  • synchronized:是 Java 语言的内置特性,语法相对简单,但灵活性较差,一旦线程进入同步块,就会一直阻塞直到获取到锁。
公平性
  • ReentrantLock:可以通过构造函数指定是否为公平锁。公平锁会按照线程请求锁的顺序依次获取锁,避免某些线程长时间得不到锁。例如 ReentrantLock fairLock = new ReentrantLock(true); 就创建了一个公平锁。
  • synchronized:是非公平锁,线程获取锁的顺序是不确定的,可能会导致某些线程长时间等待。
锁的释放
  • ReentrantLock:需要手动调用 unlock() 方法释放锁,因此必须确保在 finally 块中释放锁,以避免死锁。
  • synchronized:会在同步块或同步方法执行完毕后自动释放锁。

高级特性

tryLock() 方法

tryLock() 有两种重载形式:

  • 无参的 tryLock():尝试获取锁,如果锁可用,则获取锁并返回 true;如果锁不可用,则立即返回 false,线程不会阻塞。
  • 带超时时间的 tryLock(long timeout, TimeUnit unit):在指定的时间内尝试获取锁,如果在该时间内获取到锁则返回 true,否则返回 false
lockInterruptibly() 方法

lockInterruptibly() 方法允许线程在等待锁的过程中被中断。如果线程在等待锁的过程中被其他线程中断,会抛出 InterruptedException 异常,线程可以根据需要进行相应的处理。

适用场景

  • 当需要更灵活的锁控制,如尝试获取锁、可中断的锁等待时,使用 ReentrantLock 更合适。
  • 对于公平性有要求的场景,ReentrantLock 可以通过设置为公平锁来满足需求。
  • 若代码逻辑较为简单,对锁的灵活性要求不高,使用 synchronized 更简洁方便。

 感谢观看!!!

文章来源:https://blog.csdn.net/2301_80329517/article/details/146322917
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ppmy.cn/devtools/168214.html

相关文章

vue/react/vite前端项目打包的时候加上时间最简单版本,防止后端扯皮

如果你是vite项目&#xff0c;直接写一个vite的插件&#xff0c;通过这个插件可以动态注入环境变量&#xff0c;然后当打包的时候&#xff0c;自动注入这个时间到环境变量中&#xff0c;然后在项目中App.vue中或者Main.tsx中打印出来&#xff0c;这就知道是什么时候编译的项目了…

C51 Proteus仿真实验17:数码管显示4×4键盘矩阵按键

说明 按下任意键时&#xff0c;数码管都会显示其键的序号&#xff0c;扫描程序首先判断按键发生在哪一列&#xff0c;然后根据所发生的行附加不同的值&#xff0c;从而得到按键的序号 Proteus仿真 注意&#xff1a; K0、K4、K8、KC右边引脚连接的是P1.0 K1、K5、K9、KD右边引…

通向AGI的未来之路!首篇2D/视频/3D/4D统一生成框架全景综述(港科大中山等)

文章链接&#xff1a; https://arxiv.org/pdf/2503.04641 摘要 理解并复现现实世界是人工通用智能&#xff08;AGI&#xff09;研究中的一个关键挑战。为实现这一目标&#xff0c;许多现有方法&#xff08;例如世界模型&#xff09;旨在捕捉支配物理世界的基本原理&#xff0…

使用Ajax技术进行动态网页的爬虫(pycharm)

Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;技术在现代Web开发中广泛应用。 它允许网页在不重新加载整个页面的情况下&#xff0c;通过JavaScript与服务器进行异步通信&#xff0c;动态更新部分内容。这种技术对爬虫的功能和作用产生了显著影响&#xff0c;…

详细讲一下 Webpack 主要生命周期钩子流程(重难点)

1. Webpack 主要生命周期钩子流程 class LifecyclePlugin {apply(compiler) {// 1. 初始化阶段compiler.hooks.initialize.tap(LifecyclePlugin, () > {console.log(1. 初始化 Webpack);});// 2. 开始编译compiler.hooks.beforeRun.tap(LifecyclePlugin, () > {console.…

【后端】【django】抛弃 Django 自带用户管理后,能否使用 `simple-jwt`?

抛弃 Django 自带用户管理后&#xff0c;能否使用 simple-jwt&#xff1f; 一、结论 是的&#xff0c;即使抛弃了 Django 自带的用户管理&#xff08;AbstractUser 或 AbstractBaseUser&#xff09;&#xff0c;仍然可以使用 django-rest-framework-simplejwt&#xff08;简称…

Java中接口隔离原则简介和代码举例

简介&#xff1a; 接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;ISP&#xff09;是面向对象设计SOLID原则中的“I”&#xff0c;其核心思想是&#xff1a; 定义 客户端不应被迫依赖它不使用的方法。即&#xff0c;一个类对另一个类的依赖应建立在最…

软考中级-数据库-4.4 文件管理与作业管理

主要考点 文件管理&#xff1a; 1、文件的结构和组织 2、文件的目录结构 3、文件存储空间的管理 4、作业调度算法 文件的结构和组织 • 文件的逻辑结构&#xff1a;从用户角度看到的文件组织形式就是文件的逻辑结构&#xff0c;但实际上这些文件在内存上的存放方式可能并不是这…