2024年安卓最全征服Android面试官路漫漫(一):线程攻略,夯实基础很重要(1),2024年最新BAT大厂面试总结

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// 如果队列还没满,就创建新的Object对象,并且在队尾入列
queue.offer(new Object());
System.out.println(“生产者(线程名字:” + Thread.currentThread().getName() + “):生产了一个产品”);
// 通知其他生产者线程和消费者线程
queue.notifyAll();
}
}

void consume() {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println(“消费者(线程名字:” + Thread.currentThread().getName() + “):存储库是空”);
// 当队列空了后,调用变量queue的wait()方法,消费者线程进入等待状态,并且释放Queue对象的监视器锁
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果队列存在元素,就将队头元素出列
queue.poll();
System.out.println(“消费者(线程名字:” + Thread.currentThread().getName() + “):消费了一个产品”);
// 通知其他消费者线程和生产者线程
queue.notifyAll();
}
}

}

以下是测试代码,我先把生产者所在的线程睡眠(sleep)一秒,把消费者所在的线程睡眠三秒,这样就可以制造出生产速度大于消费速度的场景,代码如下所示:

package producerconsumerproblem;

/**

  • Created by TanJiaJun on 2020/8/30.
    */
    class ProducerConsumerProblemTest {

// 生产者线程
private static class Producer implements Runnable {

private Repository repository;

Producer(Repository repository) {
this.repository = repository;
}

@Override
public void run() {
// 循环执行
while (true) {
try {
// 让生产者线程睡眠一秒
Thread.sleep(1000);
// 调用存储库的produce()方法,生产者生产产品
repository.produce();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}

}

// 消费者线程
private static class Consumer implements Runnable {

private Repository repository;

Consumer(Repository repository) {
this.repository = repository;
}

@Override
public void run() {
// 循环执行
while (true) {
try {
// 让消费者线程睡眠三秒
Thread.sleep(3000);
// 调用存储库的consume()方法,消费者消费产品
repository.consume();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}

}

public static void main(String[] args) {
// 创建存储库
Repository repository = new Repository();

// 创建三个生产者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Producer(repository)).start();
}

// 创建三个消费者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Consumer(repository)).start();
}
}

}

运行上面的代码,大约十秒后手动结束进程,结果如下所示:

/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home/bin/java “-javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=63240:/Applications/IntelliJ IDEA CE.app/Contents/bin” -Dfile.encoding=UTF-8 -classpath /Users/tanjiajun/IdeaProjects/ThreadDemo/out/production/ThreadDemo producerconsumerproblem.ProducerConsumerProblemTest
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-3):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-1):存储库已满
生产者(线程名字:Thread-0):存储库已满
消费者(线程名字:Thread-3):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):存储库已满
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-0):存储库已满
生产者(线程名字:Thread-1):存储库已满

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

然后我把生产者所在的线程睡眠三秒,把消费者所在的线程睡眠一秒,这样就可以制造出生产速度小于消费速度的场景,代码如下所示:

package producerconsumerproblem;

/**

  • Created by TanJiaJun on 2020/8/30.
    */
    class ProducerConsumerProblemTest {

// 生产者线程
private static class Producer implements Runnable {

private Repository repository;

Producer(Repository repository) {
this.repository = repository;
}

@Override
public void run() {
// 循环执行
while (true) {
try {
// 让生产者线程睡眠三秒
Thread.sleep(3000);
// 调用存储库的produce()方法,生产者生产产品
repository.produce();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}

}

// 消费者线程
private static class Consumer implements Runnable {

private Repository repository;

Consumer(Repository repository) {
this.repository = repository;
}

@Override
public void run() {
// 循环执行
while (true) {
try {
// 让消费者线程睡眠一秒
Thread.sleep(1000);
// 调用存储库的consume()方法,消费者消费产品
repository.consume();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}

}

public static void main(String[] args) {
// 创建存储库
Repository repository = new Repository();

// 创建三个生产者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Producer(repository)).start();
}

// 创建三个消费者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Consumer(repository)).start();
}
}

}

运行上面的代码,大约十秒后手动结束进程,结果如下所示:

/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home/bin/java “-javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=63256:/Applications/IntelliJ IDEA CE.app/Contents/bin” -Dfile.encoding=UTF-8 -classpath /Users/tanjiajun/IdeaProjects/ThreadDemo/out/production/ThreadDemo producerconsumerproblem.ProducerConsumerProblemTest
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-5):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

上面的结果都符合预期,我解释一下,当发现队列满了后,就会调用变量queue的wait()方法,该生产者线程就会被进入等待状态,并且释放Queue对象的监视器锁,让其他生产者线程和消费者线程去竞争这个监视器锁,打破了死锁产生的四个条件中的请求并持有条件,避免发生死锁,同样的,当发现队列空了后,也会调用变量queue的wait()方法,该消费者线程会进入等待状态,并且释放Queue对象的监视器锁,让其他消费者线程和生产者线程去竞争这个监视器锁,打破了死锁的四个条件中的请求并持有条件,避免发生死锁。

join系列方法–等待线程执行终止

join系列方法是Thread类的一个普通方法。它可以处理一些需要等待某几个任务完成后才能继续往下执行的场景。

join()方法

源码如下所示:

// Thread.java
public final void join() throws InterruptedException {
join(0);
}

这个方法实际上调用了join(final long millis)方法,参数millis的值是0。

join(final long millis)方法

源码如下所示:

// Thread.java
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException(“timeout value is negative”);
}
}

参数millis是等待时间,单位是毫秒。

要注意的是,如果传入了负数的millis,就会抛出IllegalArgumentException异常。

join(long millis, int nanos)方法

源码如下所示:

// Thread.java
public final synchronized void join(long millis, int nanos)
throws InterruptedException {

if (millis < 0) {
throw new IllegalArgumentException(“timeout value is negative”);
}

if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
“nanosecond timeout value out of range”);
}

if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}

join(millis);
}

这个方法实际上调用了join(final long millis)方法;参数millis是等待时间,单位是毫秒;参数nanos是额外的时间,单位是纳秒,范围是0~999999(包括999999)。

只有在参数nanos大于0的时候,参数millis才会自增。

我在写深入了解volatile关键字这篇文章的时候,其中一个例子使用到了这个方法,代码如下所示:

/**

  • Created by TanJiaJun on 2020-08-16.
    */
    class VolatileDemo {

private static final int THREADS_COUNT = 10;

private static volatile int value = 0;

private static void increase() {
// 对value变量进行自增操作
value++;
}

public static void main(String[] args) {
// 创建10个线程
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++)
// 每个线程对value变量进行1000次自增操作
increase();
});
threads[i].start();
}
// 主线程等待子线程运行结束
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(“value的值:” + value);
}

}

在这个示例代码中调用join()方法目的是为了让这十个子线程运行结束后,主线程才结束,保证这十个子线程都能全部运行结束。

yield()–让出CPU执行权

yield()方法是Thread类的一个静态方法。当一个线程调用这个方法后,当前线程告诉线程调度器让出CPU执行权,但是线程调度器可以无条件忽略这个请求,如果成功让出后,线程处于就绪状态,它会从线程就绪队列中获取一个线程优先级最高的线程,当然也有可能调度到刚刚让出CPU执行权的那个线程来获取CPU执行权。源码如下所示:

// Thread.java
public static native void yield();

它和sleep()方法的区别是:当线程调用sleep()方法时,它会被阻塞指定的时间,在这个期间线程调度器不会去调度其他线程,而当线程调用yield()方法时,线程只是让出自己剩余的CPU时间片,线程还是处于就绪状态,并没有被阻塞,线程调度器在下一次调度时可能还会调度到这个线程执行。

线程中断

在Java中,线程中断是一种线程间的协作模式。

要注意的是,通过设置线程的中断标志并不能立刻终止线程的执行,而是通过被中断的线程的中断标志自行处理。

interrupt()方法

interrupt()方法可以中断线程,如果是在其他线程调用该线程的interrupt()方法,会通过checkAccess()方法检查权限,这有可能抛出SecurityException异常。假设有两个线程,分别是线程A和线程B,当线程A正在运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并且立即返回,前面也提到过,设置标志仅仅是设置标志而已,线程A实际上还在运行,还没被中断;如果线程A因为调用了wait系列方法、join()方法或者sleep()方法而被阻塞,这时候线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常。源码如下所示:

// Thread.java
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();

// 线程可能在IO操作中阻塞
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 设置中断标志
b.interrupt(this);
return;
}
}
}

// 设置中断标志
interrupt0();
}

isInterrupted()方法

isInterrupted()方法可以用来检测当前线程是否被中断,如果是就返回true,否则返回false。源码如下所示:

// Thread.java
public boolean isInterrupted() {
return isInterrupted(false);
}

interrupted()方法

interrupted()方法是Thread类的一个静态方法。它可以用来检测当前线程是否被中断,如果是就返回true,否则返回false。它和上面提到的isInterrupted()方法的不同的是:如果发现当前线程被中断,就会清除中断标志,并且这个方法是静态方法,可以直接调用Thread.interrupted()使用。源码如下所示:

// Thread.java
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

isInterrupted()方法和interrupted()方法都是调用了isInterrupted(boolean ClearInterrupted)方法,源码如下所示:

// Thread.java
@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean ClearInterrupted);

参数ClearInterrupted是用来判断是否需要重置中断标志。

从源码可得知,interrupted()方法是通过获取当前线程的中断标志,而不是获取调用interrupted()方法的实例对象的中断标志。

线程上下文切换

在多线程编程中,线程的个数一般大于CPU的个数,但是每一个CPU只能被一个线程使用,为了让用户感觉多个线程在同时执行,CPU的资源分配策略采用的是时间片轮换策略,时间片轮换策略是指给每个线程分配一个时间片,线程会在时间片内占用CPU执行任务。

线程上下文切换是指当前线程使用完时间片后,处于就绪状态,并且让出CPU给其他线程占用,同时保存当前线程的执行现场,用于再次执行时恢复执行现场。

线程死锁

线程死锁是指两个或者两个以上的线程在执行的过程中,因为互相竞争资源而导致互相等待的现象,如果没有外力的情况下,它们会一直互相等待,导致无法继续执行下去。

死锁的产生必须具备以下四个条件:

  • **互斥条件:**指资源只能由一个线程占用,如果其他线程要请求使用该资源,就只能等待,直到占用资源的线程释放该资源。
  • **请求并持有条件:**指一个线程已经占有至少一个资源,但是还想请求占用新的资源,而新的资源被其他线程占用,所以当前线程会被阻塞,但是阻塞的同时不释放自己获取的资源。
  • **不可剥夺条件:**指线程获取到的资源在自己使用完毕之前不能被其他线程占用,只有在自己使用完毕后才会释放该资源。
  • **环路等待条件:**指发生在死锁时,必然存在一个线程——资源的环形链,举个例子:有一个线程集合{Thead0, Thread1, Thread2, ……, Threadn),其中Thread0等待Thread1占用的资源,Thread1等待Thread2占用的资源,Thread2等待Thread3占用的资源,……,Threadn等待Thread0占用的资源。

那如何避免死锁呢?只要打破其中一个条件就可以避免死锁,不过基于操作系统的特性,只有请求并持有条件和环路等待条件是可以破坏的。

用户线程和守护线程

**Java中的线程分为两类:**用户线程(User Thread)和守护线程(Daemon Thread)。在Java虚拟机启动的时候会调用main方法,main方法所在的线程就是一个用户线程,同时Java虚拟机还会启动很多守护线程,例如:垃圾回收线程。

只需要调用Thread类的setDaemon(boolean on)方法,并且参数on设为true,就可以使该线程成为守护线程。

用户线程和守护线程的区别是当最后一个用户线程结束后,Java虚拟机进程才会正常结束,而守护线程是否结束不影响Java虚拟机进程的结束。

总结一下:

如果我们希望在主线程结束后,子线程继续工作,等到子线程结束后才让Java虚拟机进程结束,我们可以把线程设为用户线程;如果我们希望在主线程结束后,Java虚拟机进程也立即结束,我们可以把线程设为守护线程。

面试复习路线,梳理知识,提升储备

自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。

关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

  • 架构师筑基必备技能
  • Android高级UI与FrameWork源码
  • 360°全方面性能调优
  • 解读开源框架设计思想
  • NDK模块开发
  • 微信小程序
  • Hybrid 开发与Flutter

最后

我的面试经验分享可能不会去罗列太多的具体题目,因为我依然认为面试经验中最宝贵的不是那一个个具体的题目或者具体的答案,而是结束面试时,那一刻你的感受以及多天之后你的回味~

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

【延伸Android必备知识点】

[外链图片转存中…(img-vjYLVFxl-1715758093125)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值