2 Star 20 Fork 42

胡老皮 / 2023年Android面试题合集解析

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Java多线程面试题.md 18.76 KB
一键复制 编辑 原始数据 按行查看 历史

这套Android面试题汇总大全,希望对大家有帮助哈~

全部答案,更新日期:2月6日,直接下载吧

全部答案,更新日期:2023年2月6日,直接下载吧!

下载链接:高清全答案解析,累计935页+大厂面试题 PDF

Java多线程

1、Java 中使用多线程的方式有哪些?

大概这么4种 extends Thread; implRunnable; implCallable通过 FutureTask包装器来创建线程; 使用ExecutorService、Callable、Future实现有返回结果的多线 程。; extends Thread 和 implRunnable 的线程没有返回值, 而 FutureTask 和 ExecutorService(ExecutorService.submit(xxx) return Future<?> ) 有返回值.

2、说一下线程的几种状态?

第一是创建状态。在生成线程对象,并没有调用该对象的 start方法,这是线程处于创建状态。 第二是就绪状态。当调用了线程对象的start方法之后, 该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪 状态。 第三是运行状态。线程调度程序将处于就绪状态的线 程设置为当前线程,此时线程就进入了运行状态,开始运 行run函数当中的代码。 第四是阻塞状态。线程正在运行的时候,被暂停,通常 是为了等待某个时间的发生(比如说某项资源就绪)之后再继 续运行。sleep,suspend,wait等方法都可以导致线程阻塞。 第五是死亡状态。如果一个线程的run方法执行结束或 者调用stop方法后,该线程就会死亡。对于已经死亡的线程, 无法再使用start方法令其进入就绪。

3、如何实现多线程中的同步?

多线程同步和异步不是一回事。 几种情况, 1.就是大家说的synchronized 他可以保证原子性,保证多个 线程在操作同一方法时只有一个线程可以持有锁,并且操作 该方法, 2.就是手动调用读写锁, 3.手动操作线程的wait和notify 4.volatile我记得是没有原子性的,他可以保证内存可见性, 在多线程的情况下保证每个线程的数据都是最新的

4、谈谈线程死锁,如何有效的避免线程死锁?

一、死锁的定义 多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死 锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局 (互相等待),若无外力作用,这些进程都将无法向前推 进。 二、死锁产生的原因

  1. 系统资源的竞争 通常系统中拥有的不可剥夺资源,其数量不足以满足多个进 程运行的需要,使得进程在 运行过程中,会因争夺资源而 陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞 争 才可能产生死锁,对可剥夺资源的竞争是不会引起死锁 的。
  2. 进程推进顺序非法 进程在运行过程中,请求和释放资源的顺序不当,也同样 会导致死锁。例如,并发进程 P1、P2分别保持了资源 R1、R2,而进程P1申请资源R2,进程P2申请资源R1时, 两 者都 会因为所需资源被占用而阻塞。 3)信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,结果也会使得这 些 进程间无法继续向前推进。例如,进程A等待进程B发的消 息,进程B又在等待进程A 发的消息,可以看出进程A和B 不 是因为竞争同一资源,而是在等待对方的资源导致死 锁。
  3. 死锁产生的必要条件 产生死锁必须同时满足以下四个条件,只要其中任一条件不 成立,死锁就不会发生。 互斥条件:进程要求对所分配的资源(如打印机)进行排 他性控制,即在一段时间内某 资源仅为一个进程所占有。 此时若有其他进程请求该资源,则请求进程只能等待。 不剥夺条件:进程所获得的资源在未使用完毕之前,不能 被其他进程强行夺走,即只能 由获得该资源的进程自己 来释放(只能是主动释放)。 请求和保持条件:进程已经保持了至少一个资源,但又提 出了新的资源请求,而该资源 已被其他进程占有,此时 请求进程被阻塞,但对自己已获得的资源保持不放。循环 等待条件:存在一种进程资源的循环等待链,链中每一个 进程已获得的资源同时被 链中下一个进程所请求。
/**
* 一个简单的死锁类
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡
眠500毫秒
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启
动,先锁定o2,睡眠500毫秒* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被
td2锁定;
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被
td1锁定;
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执
行,从而死锁。
*/
public class DeadLock implements Runnable { public int flag
= 1;
//静态对象是类的所有对象共享的
private static Object o1 = new Object(), o2
= new Object();
@Override
public void run() { System.out.println("flag=" +
flag); if (flag == 1) {
synchronized (o1) { try {
Thread.sleep(500);
} catch (Exception e)
{ e.printStackTrace();
}synchronized (o2)
{ System.out.println("1");
}
}
}
if (flag == 0)
{ synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e)
{ e.printStackTrace();
}
synchronized (o1)
{ System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪
个线程是不确定的。
//td2的run()可能在td1的run()之前运行newThread(td1).start();
new Thread(td2).start();
}
}

5、如何避免死锁

在有些情况下死锁是可以避免的。三种用于避免死锁的技 术:

  1. 加锁顺序(线程按照一定的顺序加锁)Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3:
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超 过时限则放弃对该锁的请求,并释放自己占有的锁)
  3. 死锁检测 加锁顺序 当多个线程需要相同的一些锁,但是按照不同的顺序加 锁,死锁就很容易发生。 如果能确保所有的线程都是按照相同的顺序获得锁,那么 死锁就不会发生。看下面这个例子:
Thread 1:
lock A
lock B
Thread 2:
wait for A
lock C (when A locked)
Thread 3:
2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超
过时限则放弃对该锁的请求,并释放自己占有的锁)
3. 死锁检测
加锁顺序
当多个线程需要相同的一些锁,但是按照不同的顺序加
锁,死锁就很容易发生。
如果能确保所有的线程都是按照相同的顺序获得锁,那么
死锁就不会发生。看下面这个例子:
wait for A
wait for B
wait for C

如果一个线程(比如线程3)需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之 后,才能获取后面的锁。例如,线程2和线程3只有在获取了锁A之后才能尝试获取锁C(译者注:获取锁A是获取锁C的必要条件)。因为线程1已 经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然 后在它们尝试对B或C加锁之前,必须成功地对A加了锁。按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁(译者注:并对这些锁做适当的排序),但总有些时候是无法预知的。加锁时限另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。以下是一个例子,展示了两个线程以不同的顺序尝试获取 相同的两个锁,在发生超时后回退并重试的场景:

Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis)
before retrying. Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis)
before retrying.

在上面的例子中,线程2比线程1早200毫秒进行重试加锁,因此它可以先成功地获取到两个锁。这时,线程1尝试获取锁A并且处于等待状态。当线程2结束时,线程1也可以顺利的获得这两个锁(除非线程2或者其它线程在线程1成功获得两个锁之前又获得其中的一些锁)。需要注意的是,由于存在锁的超时,所以我们不认为这种场景就一定是出现了死锁。也可能是因为获得了锁的线程(导致其它线程超时)需要很长的时间去完成它的任 务。此外,如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)。(译者注:超时和重试机制是为了避免在同一时间出现的竞争,但是当线程很多时,其中两个或多个线程的超时时间一样或者接近的可能性就会很大,因此就算出现竞争而导致超时后,由于超时时间一样,它们又会同时开始重试, 导致新一轮的竞争,带来了新的问题。)这种机制存在一个问题,在Java中不能对synchronized同步块设置超时时间。你需要创建一个自定义锁,或使用Java5中java.util.concurrent包下的工具。写一个自定义锁类不复杂,但超出了本文的内容。后续的Java并发系列会涵盖自定义锁的内容。 死锁检测 死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程 请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图 看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7; 线程B拥有锁7,请求锁1)。当然,死锁一般要比两个线程互相持有对方的锁这种情况 要复杂的多。线程A等待线程B,线程B等待线程C,线程C 等待线程D,线程D又在等待线程A。线程A为了检测死锁, 它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求 的关系图。像这样的数据结构就可以被用来检测死锁。

在这里插入图片描述

那么当检测出死锁时,这些线程该做些什么呢?一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

6、请谈谈 Thread 中 run() 与 start()的区别?

run() 和普通的成员方法一样,可以被重复调用。但是如果单独调用 run 方法,则不是在子线程中执行。start()这个方法只能被调用一次。调用这个方法后 程序会启动一个 新的线程来 执行 run 方法。注意 :调用start 后,线程处于可运行状态(并没有运行),一旦得到 cup 时间片,就开始执行run 方法,run 方法结束后,线程则立即终止。

7、synchronized和volatile关键字的区别?

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 2.volatile仅能使用在变量级别;synchronized则可以使用 在变量、方法、和类级别的volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性 3.volatile不会造成线程的阻塞;synchronized可能会造成 线程的阻塞。 4.volatile标记的变量不会被编译器优化;synchronized标 记的变量可以被编译器优化

8、如何保证线程安全?

当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:

原子性(Synchronized, Lock)

有序性(Volatile,Synchronized, Lock) 可见性(Volatile,Synchronized,Lock)

当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。 下面是对3个要素的详细解释: 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,基本数据类型的变量的读取和赋值操作是原子 性操作,即这些操作是不可被中断的,要么执行,要么不执行。

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。当一个共享变量被volatile修饰时,它会保证修改的值会立 即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。更新主存的步骤:当前线程将其他线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到主存,更新成功后将其他线程的缓存行更新为新的主存地址其他线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

有序性:即程序执行的顺序按照代码的先后顺序执行。 在Java内存模型中,允许编译器和处理器对指令进行重排 序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。可以通过volatile关键字来保证一定的“有序性”。当在处理并发编程的时候,只要程序满足了原子性,可见性和有序性,那么程序就不会发生脏数据的问题。

9、谈谈ThreadLocal用法和原理?

ThreadLocal用于保存某个线程共享变量:对于同一个staticThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的 值。 2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的 值。 3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法, 返回此方法值。

10、Java 线程中notify 和 notifyAll有什么区别?

notify 方 法 1.方法调用之前由于也需要获取该对象的锁,所以使用的位置: synchronized方法中或者synchronized代码块中 2.通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait() 状 态 的 线 程 来 发 出 通 知 3.wait()方法执行完毕之后,会立刻释放掉锁,如果没有再 次使用notify,其他wait()的线程由于没有得到通知,会继续阻塞在wait()的状态,等待其他对象调用notify或者notifyAll来唤醒。 notifyAll方法 1.使用位置和notify一样 2.notifyAll唤醒所有处于wait的线程

11、什么是线程池?如何创建一个线程池?

12、谈一谈java线程常见的几种锁?

13、谈一谈线程sleep()和wait()的区别?

14、什么是悲观锁和乐观锁?

15、什么是BlockingQueue?请分析一下其内部原理并谈谈它的使用场景?

16、谈一谈java线程安全的集合有哪些?

17、Java中为什么会出现Atomic类?试分析它的原理和缺点?

18、说说ThreadLocal的使用场景?与Synchronized相比有什么特性?

由于篇幅原因,此处仅展示部分内容,查看更多Android面试题点击直接查看

全部答案,更新日期:2023年2月6日,直接下载吧!

下载链接:全部答案,整理好了

新增:高清全答案解析,累计935页+大厂面试题 PDF

1
https://gitee.com/hu-laopi/NewDevBooks.git
git@gitee.com:hu-laopi/NewDevBooks.git
hu-laopi
NewDevBooks
2023年Android面试题合集解析
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891