2020最后一天! 我为大家准备一份Android 面试知识点大全迎接2021新的一年

线程、多线程和线程池面试

1、开启线程的三种方式?

1)继承 Thread 类,重写 run()方法,在 run()方法体中编写要完成的任务 new Thread().start();

2 ) 实 现 Runnable 接 口 , 实 现 run() 方 法 new Thread(new MyRunnable()).start();

3)实现 Callable 接口 MyCallable 类,实现 call()方法,使用 FutureTask 类来 包装 Callable 对象,使用 FutureTask 对象作为 Thread 对象的 target 创建并启 动线程;调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值。

FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable()); 
new Thread(ft).start(); 

2、run()和 start()方法区别

run()方法只是线程的主体方法,和普通方法一样,不会创建新的线程。只有调 用 start()方法,才会启动一个新的线程,新线程才会调用 run()方法,线程才会 开始执行。

3、如何控制某个方法允许并发访问线程的个数?

创建 Semaphore 变量,Semaphore semaphore = new Semaphore(5, true); 当方法进入时,请求一个信号,如果信号被用完则等待,方法运行完,释放一个 信号,释放的信号新的线程就可以使用。

4、在 Java 中 wait 和 seelp 方法的不同

wait()方法属于 Object 类,调用该方法时,线程会放弃对象锁,只有该对象调 用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

sleep()方法属于 Thread 类,sleep()导致程序暂停执行指定的时间,让出 CPU, 但它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep()方法 中线程不会释放对象锁。

5、谈谈 wait/notify

关键字的理解 notify: 唤醒在此对象监视器上等待的单个线程

notifyAll(): 通知所有等待该竞争资源的线程

wait: 释放 obj 的锁,导致当前的线程等待,直接其他线程调用此对象的 notify() 或 notifyAll()方法

当要调用 wait()或 notify()/notifyAll()方法时,一定要对竞争资源进行加锁,一般放到 synchronized(obj)代码中。当调用 obj.notify/notifyAll 后,调用线程 依旧持有 obj 锁,因此等待线程虽被唤醒,但仍无法获得 obj 锁,直到调用线程 退出 synchronized 块,释放 obj 锁后,其他等待线程才有机会获得锁继续执行。

6、什么导致线程阻塞?

一般线程阻塞

1)线程执行了 Thread.sleep(int millsecond)方法,放弃 CPU,睡眠一段时间, 一段时间过后恢复执行;

2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等 到获取到同步锁,才能恢复执行;

3)线程执行了一个对象的 wait()方法,直接进入阻塞态,等待其他线程执行 notify()/notifyAll()操作;

4)线程执行某些 IO 操作,因为等待相关资源而进入了阻塞态,如 System.in, 但没有收到键盘的输入,则进入阻塞态。

5)线程礼让,Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会 让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行 态,随时可能再次分得 CPU 时间。线程自闭,join()方法,在当前线程调用另一
个线程的 join()方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前 线程再由阻塞转为就绪态。

6)线程执行 suspend()使线程进入阻塞态,必须 resume()方法被调用,才能使 线程重新进入可执行状态。

7、线程如何关闭?

1)使用标志位

2)使用 stop()方法,但该方法就像关掉电脑电源一样,可能会发生预料不到的 问题

3)使用中断 interrupt()

public class Thread { 
		// 中断当前线程 
		public void interrupt(); 
		// 判断当前线程是否被中断 
		public boolen isInterrupt(); 
		// 清除当前线程的中断状态,并返回之前的值 
		public static boolen interrupted(); 
	}

但调用 interrupt()方法只是传递中断请求消息,并不代表要立马停止目标线程。

8、讲一下 java 中的同步的方法

之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资 源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确 保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一性和准确 性。

1)synchronized 修饰同步代码块或方法

由于 java 的每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护 整个方法。在调用该方法前,需获得内置锁,否则就处于阴塞状态。

2)volatile 修饰变量

保证变量在线程间的可见性,每次线程要访问 volatile 修饰的变量时都从内存中 读取,而不缓存中,这样每个线程访问到的变量都是一样的。且使用内存屏障。

3)ReentrantLock 重入锁,它常用的方法有 ReentrantLock():创建一个 ReentrantLock 实例

lock()获得锁 unlock()释放锁

4)使用局部变量 ThreadLocal 实现线程同步,每个线程都会保存一份该变量的 副本,副本之间相互独立,这样每个线程都可以随意修改自己的副本,而不影响 其他线程。常用方法 ThreadLocal()创建一个线程本地变量;get()返回此线程局 部的当前线程副本变量;initialValue()返回此线程局部变量的当前线程的初始 值;set(T value)将此线程变量的当前线程副本中的值设置为 value

5)使用原子变量,如 AtomicInteger,常用方法 AtomicInteger(int value)创 建个有给定初始值的 AtomicInteger 整数;addAndGet(int data)以原子方式 将给定值与当前值相加

6)使用阻塞队列实现线程同步 LinkedBlockingQueue

9、如何保证线程安全? 线程安全性体现在三方法:

1)原子性:提供互斥访问,同一时刻只能有一个线和至数据进行操作。 JDK 中 提 供 了 很 多 atomic 类 , 如 AtomicInteger\AtomicBoolean\AtomicLong,它们是通过 CAS 完成原子性。 JDK 提供锁分为两种:synchronized 依赖 JVM 实现锁,该关键字作用对象的 作用范围内同一时刻只能有一个线程进行操作。另一种是 LOCK,是 JDK 提供的
代码层面的锁,依赖 CPU 指令,代表性是 ReentrantLock。

2)可见性:一个线程对主内存的修改及时被其他线程看到。

JVM 提供了 synchronized 和 volatile,volatile 的可见性是通过内存屏障和禁 止重排序实现的,volatile 会在写操作时,在写操作后加一条 store 屏障指令, 将本地内存中的共享变量值刷新到主内存;会在读操作时,在读操作前加一条 load 指令,从内存中读取共享变量。

3)有序性:指令没有被编译器重排序。

可通过 volatile、synchronized、Lock 保证有序性。

10、两个进程同时要求写或者读,能不能实现?如何防止进程的同步?

我认为可以实现,比如两个进程都读取日历进程数据是没有问题,但同时写,应 该会有冲突。

可以使用共享内存实现进程间数据共享。

11、线程间操作 List

12、Java 中对象的生命周期

1)创建阶段(Created):为对象分配存储空间,开始构造对象,从超类到子 类对 static 成员初始化;超类成员变量按顺序初始化,递归调用超类的构造方法, 子类成员变量按顺序初始化,子类构造方法调用。

2)应用阶段(In Use):对象至少被一个强引用持有着。

3)不可见阶段(Invisible):程序运行已超出对象作用域

4)不可达阶段(Unreachable):该对象不再被强引用所持有

5)收集阶段(Collected):假设该对象重写了 finalize()方法且未执行过,会 去执行该方法。

6)终结阶段(Finalized):对象运行完 finalize()方法仍处于不可达状态,等待 垃圾回收器对该对象空间进行回收。

7)对象空间重新分配阶段(De-allocated):垃圾回收器对该对象所占用的内 存空间进行回收或再分配,该对象彻底消失。

13、static synchronized 方法的多线程访问和作用

static synchronized 控制的是类的所有实例访问,不管 new 了多少对象,只有一份,所以对该类的所有对象都加了锁。限制多线程中该类的所有实例同时访问 JVM 中该类对应的代码。

14、同一个类里面两个 synchronized 方法,两个线程同时访问的问题

如果 synchronized 修饰的是静态方法,锁的是当前类的 class 对象,进入同步 代码前要获得当前类对象的锁;

普通方法,锁的是当前实例对象,进入同步代码前要获得的是当前实例的锁;

同步代码块,锁的是括号里面的对象,对给定的对象加锁,进入同步代码块库前 要获得给定对象锁;

如果两个线程访问同一个对象的 synchronized 方法,会出现竞争,如果是不同 对象,则不会相互影响。

15、volatile 的原理

有volatile变量修饰的共享变量进行写操作的时候会多一条汇编代码,lock addl $0x0,lock 前缀的指令在多核处理器下会将当前处理器缓存行的数据会写回到 系统内存,这个写回内存的操作会引起在其他 CPU 里缓存了该内存地址的数据 无效。同时 lock 前缀也相当于一个内存屏障,对内存操作顺序进行了限制。

16、synchronized 原理

synchronized 通过对象的对象头(markword)来实现锁机制,java 每个对象都 有对象头,都可以为 synchronized 实现提供基础,都可以作为锁对象,在字节 码层面 synchronized 块是通过插入 monitorenter monitorexit 完成同步的。 持有 monitor 对象,通过进入、退出这个 Monitor 对象来实现锁机制。

17、谈谈 NIO 的理解

NIO( New Input/ Output) 引入了一种基于通道和缓冲区的 I/O 方式,它可以 使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。 NIO 是一种同步非阻塞的 IO 模型。同步是指线 程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时 做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事 件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区, 当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞 式地等待。

-synchronized 和 volatile 关键字的区别
-synchronized 与 Lock 的区别
-ReentrantLock 、synchronized 和 volatile 比较

1)volatile:解决变量在多个线程间的可见性,但不能保证原子性,只能用于修 饰变量,不会发生阻塞。volatile 能屏蔽编译指令重排,不会把其后面的指令排 到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。多用于并行
计算的单例模式。volatile 规定 CPU 每次都必须从内存读取数据,不能从 CPU 缓存中读取,保证了多线程在多 CPU 计算中永远拿到的都是最新的值。

2)synchronized:互斥锁,操作互斥,并发线程过来,串行获得锁,串行执行 代码。解决的是多个线程间访问共享资源的同步性,可保证原子性,也可间接保 证可见性,因为它会将私有内存和公有内存中的数据做同步。可用来修饰方法、 代码块。会出现阻塞。synchronized 发生异常时,会自动释放线程占有的锁, 因此不会导致死锁现象发生。非公平锁,每次都是相互争抢资源。

3)lock 是一个接口,而 synchronized 是 java 中的关键字,synchronized 是 内置语言的实现。lock 可以让等待锁的线程响应中断。在发生异常时,如果没 有主动通过 unLock()去释放锁,则可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。

4)ReentrantLock 可重入锁,锁的分配机制是基于线程的分配,而不是基于方 法调用的分配。ReentrantLock 有 tryLock 方法,如果锁被其他线程持有,返 回 false,可避免形成死锁。对代码加锁的颗粒会更小,更节省资源,提高代码 性能。ReentrantLock 可实现公平锁和非公平锁,公平锁就是先来的先获取资 源。ReentrantReadWriteLock 用于读多写少的场合,且读不需要互斥场景。 -ReentrantLock 的内部实现 -lock 原理

  • 死锁的四个必要条件?
  • 怎么避免死锁?
  • 对象锁和类锁是否会互相影响?
  • 什么是线程池,如何使用?
  • Java 的并发、多线程、线程模型
  • 谈谈对多线程的理解 -多线程有什么要注意的问题?
  • 谈谈你对并发编程的理解并举例说明
  • 谈谈你对多线程同步机制的理解?
  • 如何保证多线程读写文件的安全?
  • 多线程断点续传原理 -断点续传的实现

5)并发编程有关知识点(这个是一般 Android 开发用的少的,所以建议多 去看看):

平时 Android 开发中对并发编程可以做得比较少,Thread 这个类经常会用到, 但是我们想提升自己的话,一定不能停留在表面,,我们也应该去了解一下 java 的关于线程相关的源码级别的东西。

面试合集

性能优化版块

1、图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处

理?

  • 参考回答:
    • 首先我们要清楚图片的三级缓存是如何的

      如果内存足够时不回收。内存不够时就回收软引用对象

2、内存中如果加载一张 500*500 的 png 高清图片.应该是占用多少的内存?

  • 参考回答:
    • 不考虑屏幕比的话:占用内存=500 * 500 * 4 = 1000000B ≈ 0.95MB
    • 考虑屏幕比的的话:占用内存= 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x(inTargetDensity / inDensity)x 一个像素所占的内存字节大小
    • inDensity 表示目标图片的 dpi(放在哪个资源文件夹下),inTargetDensity 表示目标屏幕的 dpi

3、WebView 的性能优化 ?

  • 参考回答:
    • 一个加载网页的过程中,native、网络、后端处理、CPU 都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理
      而不是相互阻塞才可以让网页加载更快:
      • WebView 初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
      • 常用 JS 本地化及延迟加载,使用第三方浏览内核
      • 后端处理慢,可以让服务器分 trunk 输出,在后端计算的同时前端也加载网络静态资源。
      • 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
      • 同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
      • WebView 初始化慢,就随时初始化好一个 WebView待用。
      • DNS 和链接慢,想办法复用客户端使用的域名和链接。

4、Bitmap 如何处理大图,如一张 30M 的大图,如何预防 OOM?

  • 参考回答:避免 OOM 的问题就需要对大图片的加载进行管理,主要通过缩放来减小图片的内存占用。
    • BitmapFactory 提供的加载图片的四类方法(decodeFile、decodeResource、decodeStream、decodeByteArray
      都支持 BitmapFactory.Options 参数,通过 inSampleSize 参数就可以很方便地对一个图片进行采样缩放
    • 比如一张 10241024 的高清图片来说。那么它占有的内存为102410244,即 4MB,如果 inSampleSize 为 2,那么采样后
      的图片占用内存只有 512512*4,即 1MB(注意:根据最新的官方文档指出,inSampleSize 的取值应该总是为 2 的指数,即
      1、2、4、8 等等,如果外界输入不足为 2 的指数,系统也会默认选择最接近 2 的指数代替,比如 2
    • 综合考虑。通过采样率即可有效加载图片,流程如下
      • 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true 并加载图片
      • 从 BitmapFactory.Options 中取出图片的原始宽高信息,它们对应 outWidth 和 outHeight 参数
      • 根据采样率的规则并结合目标 View 的所需大小计算出采样率 inSampleSize
      • 将 BitmapFactory.Options 的inJustDecodeBounds 参数设为 false,重新加载图片

5、内存回收机制与 GC 算法(各种算法的优缺点以及应用场景);GC 原理时机

以及 GC 对象

  • 参考回答:
    • 内存判定对象可回收有两种机制:
      • 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1;当引用失效时,计数器值就减 1;任何时刻计数器为 0 的对象就是不可能再被使用的。然而在主流的 Java 虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。
      • 可达性分析法:通过一系列被称为『GCRoots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的
        路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。其中可作为 GC Roots 的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量*、本地方法栈中 Native 方法引用的对
        象、方法区中类静态属性引用的对象、方法区中常量引用的对象
    • GC 回收算法有以下四种:
      • 分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将 Java 堆划分为新生代和老
        年代,并根据各个年代的特点采用最适当的收集算法。
      • 新生代:大批对象死去,只有少量存活。使用『复制算法』,只需复制少量存活对象即可。
        • 复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。实现简单,运行高效。在对象存活率较高时就要进行较
          多的复制操作,效率将会变低
      • 老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。
        • 标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。标记和清除两个过程的效率都不高,清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
      • 标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片

6、内存泄露和内存溢出的区别 ?AS 有什么工具可以检测内存泄露

  • 参考回答:
    • 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory;比如申请了一
      个 integer,但给它存了 long 才能存下的数,那就是内存溢出。
    • 内存泄露(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致 out of memory!
    • 查找内存泄漏可以使用 Android Studio 自带的AndroidProfiler工具或 MAT

7、性能优化,怎么保证应用启动不卡顿? 黑白屏怎么处理?

  • 参考回答:
    • 应用启动速度,取决于你在 application 里面时候做了什么事情,比如你集成了很多 sdk,并且 sdk 的 init 操作都需要在主
      线程里实现所以会有卡顿的感觉。在非必要的情况下可以把加载延后或则开启子线程处理
    • 另外,影响界面卡顿的两大因素,分别是界面绘制和数据处理。
      • 布局优化(使用 include,merge 标签,复杂布局推荐使用 ConstraintLayout 等)
      • onCreate() 中不执行耗时操作 把页面显示的 View 细分一下,放在 AsyncTask 里逐步显示,用 Handler更好。这样用户的看到的就是有层次有步骤的一个个的View 的展示,不会是先看到一个黑屏,然后一下显示所有 View。最好做成动画,效果更自然。s
      • 利用多线程的目的就是尽可能的减少 onCreate() 和onReume() 的时间,使得用户能尽快看到页面,操作页面。
      • 减少主线程阻塞时间。
      • 提高 Adapter 和 AdapterView 的效率。
    • 黑白屏产生原因:当我们在启动一个应用时,系统会去检查是否已经存在这样一个进程,如果不存在,系统的服务会先检查startActivity 中的 intent 的信息,然后在去创建进程,最后启动 Acitivy,即冷启动。而启动出现白黑屏的问题,就是在这段
      时间内产生的。系统在绘制页面加载布局之前,首先会初始化窗口(Window),而在进行这一步操作时,系统会根据我们设
      置的 Theme 来指定它的 Theme 主题颜色,我们在 Style 中的设置就决定了显示的是白屏还是黑屏。
      • windowIsTranslucent 和 windowNoTitle,将这两个属性都设置成 true (会有明显的卡顿体验,不推荐)
      • 如果启动页只是是一张图片,那么为启动页专一设置一个新的主题,设置主题的android:windowBackground 属性为启动页背景图即可
      • 使用 layer-list 制作一张图片 launcher_layer.xml,将其设置为启动页专一主题的背景,并将其设置为启动页布局的背景。

8、强引用置为 null,会不会被回收?

  • 参考回答:
    • 不会立即释放对象占用的内存。 如果对象的引用被置为 null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safepoint)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize 方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。

9、ListView 跟 RecyclerView 的区别

  • 参考回答:
    • 动画区别:
      • 在 RecyclerView 中,内置有许多动画 API,例如:notifyItemChanged(), notifyDataInserted(),notifyItemMoved()等等;如果需要自定义动画效果,可以通过实现(RecyclerView.ItemAnimator 类)完成自定义动画效果,然后调用RecyclerView.setItemAnimator();
      • 但是 ListView 并没有实现动画效果,但我们可以在Adapter 自己实现 item 的动画效果;
    • 刷新区别:
      • ListView 中通常刷新数据是用全局刷新notifyDataSetChanged(),这样一来就会非常消耗资源;本身无法实现局部刷新,但是如果要在 ListView 实 现局部刷新,依然是可以实现的,当一个 item 数据刷新时,我们可以在 Adapter 中,实现一个onItemChanged()方法,在方法里面获取到这个 item的 position(可以通过 getFirstVisiblePosition()),然后调用 getView()方法来刷新这个 item 的数据;
      • RecyclerView 中可以实现局部刷新,例如:notifyItemChanged();
    • 缓存区别:
      • RecyclerView 比 ListView 多两级缓存,支持多个离ItemView 缓存,支持开发者自定义缓存处理逻辑,支持所有 RecyclerView 共用同一个RecyclerViewPool(缓存池)。
      • ListView 和 RecyclerView 缓存机制基本一致,但缓存使用不同

10、ListView 的 adapter 是什么 adapter

  • 参考回答:
    • BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个适配器!
    • ArrayAdapter:支持泛型操作,最简单的一个适配器,只能展现一行文字〜
    • SimpleAdapter:同样具有良好扩展性的一个适配器,可以自定义多种效果!
    • SimpleCursorAdapter:用于显示简单文本类型的 listView,
    • 一般在数据库那里会用到,不过有点过时,不推荐使用!

11、LinearLayout、FrameLayout、RelativeLayout 性能对比,为什么?

  • 参考回答:
    • RelativeLayout 会让子 View 调用 2 次 onMeasure,LinearLayout 在有 weight 时,也会调用子 View 2 次 onMeasure
    • RelativeLayout 的子 View 如果高度和 RelativeLayout 不同,则会引发效率问题,当子 View 很复杂时,这个问题会更加严重。如果可以,尽量使用 padding 代替 margin。
    • 在不影响层级深度的情况下,使用 LinearLayout 和FrameLayout 而不是 RelativeLayout。

最后

由于文章篇幅长度有限,我就不一一的在此展现,如有小伙伴需要参考学习这份PDF文档或更多Android 相关的学习资料,可以加入Android 粉丝裙:872206502 联系管理员免费获取;



快速加入粉丝裙通道请QQ扫码

猜你喜欢

转载自blog.csdn.net/dongrimaomaoyu/article/details/112012817
今日推荐