Android 面试题整理总结(三)Java 并发

零、目录

一、并发基础
1、Java 有哪几种创建新线程的方法及区别?销毁一个线程的方法呢?
2、线程的生命周期?
3、Thread 的 join 有什么作用?
4、sleep 和 wait 的区别?
5、多线程的使用场景?为什么要使用多线程?多线程需要注意的问题?(上下文开销、死锁等)
6、多线程是否一定会高效?关于并发理解多少?
7、Java 多线程之间如何通信?除了 notify 还有什么别的方式可以唤醒线程?

二、线程池
1、线程池的几个参数的理解?
2、线程池核心线程数一般定义多少,为什么?
3、线程池中核心线程数跟最大线程数如何定义?
4、四种线程池的使用场景?
5、线程池的实现原理?
6、自定义线程池应该如何设计?

三、并发集合
1、说几个并发的集合?
2、Concurrenthashmap 的插入操作是直接操作数组中的链表吗?
3、ConcurrentHashMap 的原理,分的段数是多少?
4、ConcurrentHashMap 是如何实现分段锁的?

四、线程安全
1、什么是线程安全?如何保证线程安全?
2、同步和非同步、阻塞和非阻塞的概念?
3、lock 语句和 synchronize 对比?ReentrantLock 和 synchronized 的区别?
4、volatile 关键字的作用?
5、synchronized 同步代码块还有同步方法本质上锁住的是谁?为什么?
6、synchronized、volatile 关键字有什么区别?还有哪些具有类似功能的关键字?
7、乐观锁、悲观锁概念及使用场景?
8、静态同步锁与普通同步锁的区别?
9、Java 中的锁,各种同步方式之间的区别?
10、不用锁如何保证 int 自增安全?
11、JVM 锁的优化,偏向锁、轻量级锁概念及原理?
12、volatile 原理?
13、sycronized 原理?
13、ReentrantLock,AQS 的底层实现?
15、对 Java 内存模型的理解?
16、对 Java 对象头、markword 的理解?
16、ThreadLocal 的理解?

一、并发基础

1、Java 有哪几种创建新线程的方法及区别?销毁一个线程的方法呢?

创建线程的方法:Thread、Runnable、FutureTask(Callable)、Executors,其实最终都需要通过 Thread 来完成,比如 FutureTask,比如不配合 Thread 使用的话,仍然无法创建新线程

销毁线程:

  1. Thread 无法销毁,只能中断

  2. FutureTask 可以调用 cancel 方法取消,内部实际上调用的是 interrupt

  3. ExecutorService 可以调用方法 shutdown 或 shutdownNow 结束线程,内部实际上调用的仍然是 interrupt

总结:只能调用 interrupt 方法结束线程,无法销毁线程。

2、线程的生命周期?

《Java 编程思想》:

  1. 新建。创建时只会短暂处于这种状态,已分配了必需的系统资源,并执行了初始化。

  2. 就绪。这种状态下,只要线程调度器把时间片分配给线程,线程就可以运行。

  3. 阻塞。线程可以运行,但被某个条件阻止了,调度器将忽略该线程,不会分配 CPU 时间。

  4. 死亡。任务已结束,不再是可运行的,但还可以被中断。

《深入理解 Java 虚拟机》:
在这里插入图片描述

3、Thread 的 join 有什么作用?

阻塞直到线程运行结束或等待时间到期:



public class Thread implements Runnable {

    private final Object lock = new Object();

    public final void join(long millis) throws InterruptedException {
        synchronized(lock) {
        long base = System.currentTimeMillis();
        long now = 0;

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

        if (millis == 0) { // 时间不限
             // 等待直接线程执行完毕
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
             // 等待直接线程执行完毕或等待时间到期
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
}

4、sleep 和 wait 的区别?

sleep 会阻塞直到等待时间结束,或抛出 interrupt 异常,并一直持有对象锁

wait 会阻塞直到被 notify 唤醒,或等待时间结束,或抛出 interrupt 异常,只能在 synchronized 内部调用,会释放对象锁,唤醒后重新获取该锁,获取失败则再次进入阻塞状态

5、多线程的使用场景?

使用场景:

  1. IO,包括网络请求、文件读写等会阻塞主线程的

  2. 耗费时间较长的任务,比如音视频编解码

  3. 后台任务,比如定期更新某些数据的任务、持续监控采集信息的任务

  4. ……

6、多线程是否一定会高效?线程的开销?

不是。

线程带来的开销有:

  1. 上下文切换。如果可运行的线程大于 CPU 的数量,那么操作系统会把某个正在运行的线程调度出来,从而使其它线程能够使用 CPU,这将导致一次上下文切换,在这个过程中将保存当前运行线程的执行上下文,并将新调度进来的线程的执行上下文设置为当前上下文。

  2. 资源消耗。活跃的线程会消耗系统资源,尤其是内存;空闲的线程会占用许多内存,大量线程在竞争 CPU 资源时还将产生其他的性能开销。

  3. 稳定性。可创建的线程数量是有限的,如果超过会抛出 OOM 异常。

  4. 系统调用。 系统调用的代价较高,且需要在用户态和内核态中来回切换,会消耗一定的内核资源,包括线程之间的协调(锁、信号、同步)、线程的创建于与销毁、线程的调度等。

7、Java 多线程之间如何通信?除了 notify 还有什么别的方式可以唤醒线程?

通信方式:

  1. wait、notify

  2. 生产者消费者队列。BlockingQueue

  3. 管道。PipedReader、PipedWriter

  4. yield。建议调用器把 CPU 时间分配给其它线程,可以忽略。

wait 被唤醒的方式:

  1. notify

  2. 中断线程

  3. 设置等待时间

二、线程池

1、线程池的几个参数的理解?

ThreadPoolExecutor 的构造方法如下:



public class ThreadPoolExecutor extends AbstractExecutorService {

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        ...
    }
    
}
  1. corePoolSize:: 核心线程数, 除非设置了 allowCoreThreadTimeOut, 否则会一直存在于线程池中

  2. maximumPoolSize: 线程池允许存在的最大线程数

  3. keepAliveTime: 非核心线程能够存活的最长时间,如果设置了 allowCoreThreadTimeOut,那么也将对核心线程产生作用

  4. unit: keepAliveTime 的时间单位

  5. workQueue: 存储线程的工作队列。实现类有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 等。

  6. threadFactory: 用于创建线程的工厂类。默认的线程工厂(ThreadFactory)将创建一个新的非守护(后台)线程,不包含特殊的配置信息。

  7. hanlder: 饱和策略。工作队列被填满之后,如果继续添加任务,饱和策略就开始发挥作用。Java 提供了 AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 四种。

2、线程池大小要怎么设置?

线程池大小应该根据 CPU 个数(Runtime.availableProcessors)来动态计算,避免过大或过小,过大将导致大量线程在相对很少的 CPU 和 内存资源上发生竞争;过少将导致许多空闲的处理器无法执行工作。

要正确设置线程池的大小,必须分析计算环境、资源预算和任务的特性,然后估算出任务的等待时间与计算时间的比值,再进行调节。

  1. 对于计算密集型的任务,当核心线程数设置为 Runtime.availableProcessors + 1 时,能实现最优的利用率。因为当计算密集型的线程偶尔由于页缺失故障或者其他原因暂停时,这个额外的线程也能确保 CPU 的始终周期不会被浪费。

  2. 对于包含 IO 操作或者其它阻塞操作的任务,由于线程并不会一直执行,因此线程池的规模应该更大。

《Java 并发编程》给出了一个计算公式:

Count = CPU 个数 * 目标 CPU 利用率( 0 ~ 1) * (1 + 等待时间 / 计算时间)

3、线程池中核心线程数跟最大线程数如何定义?为什么?

// TODO 如果有更好的解答,欢迎指出
核心线程数、最大线程数、keepAliveTime 共同管理者线程的创建和销毁。

核心线程的大小是线程池试图维护的池大小,即使没有任务执行,线程池的大小也等于核心池的大小,并且直到工作队列被充满前,池都不会创建更多的线程。个人理解,可以按照第 9 题所说的来设置。

最大线程数的设置该和工作队列一起考虑,如果是 SynchronousQueue,最大线程数通常设置为 Integer.MAX_VALUE,适合处理大量的耗时较少的任务;如果是无界队列,最大线程数则应该和核心线程数一致,因为非核心线程永远也不会被创建;如果是有界队列,则要慢慢调整。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值