Android面试题-多线程(99题)

Android面试题之多线程,包括线程、Java同步问题、阻塞队列、线程池、AsyncTask、HandlerThread、IntentService等内容。

本文是我一点点归纳总结的干货,但是难免有疏忽和遗漏,希望不吝赐教。
转载请注明链接:https://blog.csdn.net/feather_wch/article/details/81207725

有帮助的话请点个赞!万分感谢!

Android面试题-多线程(99题)

2018/8/13(23:45)

进程

1、什么是进程

  1. 系统分配资源的最小单位
  2. 进程就是程序运行的实体

线程

2、什么是线程

  1. 系统调度的最小单位
  2. 一个进程中可以包含多个线程
  3. 线程拥有各自的计数器、堆栈和局部变量等属性,能够访问共享的内存变量

3、线程的好处

  1. 比进程的资源消耗要小,效率要更高
  2. 多线程的并发性能减少程序的响应时间
  3. 多线程能简化程序的结构,使程序便于理解和维护

4、线程的状态有哪些?

状态 解释 备注
New 新创建状态 线程已被创建,还未调用start,做一些准备工作
Runnable 可运行状态 start之后进入,Runnable线程可能在运行也可能没有在运行,取决于系统分配的时间
Blocked 阻塞状态 线程被锁阻塞,暂时不活动
Waiting 等待状态 线程不运行任何代码,消耗最少的资源,直至调度器激活该线程
Timed Waiting 超时等待状态 Waiting不同在于,可以在指定时间内返回
Terminated 终止状态 当前线程执行完毕:可能是run运行结束,或者出现未能捕获的异常导致线程终止

5、线程如何从新建状态进入可运行状态

Thread.start()

6、线程如何从可运行状态阻塞状态

  1. 线程在请求锁的时候会进入阻塞状态
  2. 线程一旦得到锁会返回到可运行状态

备注:

如下题目中的Object.wait()是指具体对象调用wait等方法---someObject.wait()Thread.join是指具体线程调用该方法—childThread.join()

7、线程如何从可运行状态切换到等待状态

  1. 进入等待:Object.wait()---当前线程进入等待状态(当前线程需要已经获得过锁,且调用后会失去锁)、Thread.join()---父线程会等待子线程
  2. 退出:Object.notify()和Object.notifyAll()

8、线程如何从可运行状态切换到超时等待状态

  1. 进入:Thread.sleep(long)、Thread.join(long)---让父线程等待子线程,子线程结束后父线程才继续执行、Object.wait(long)
  2. 退出:Object.notify()、Object.notifyAll()或者超时退出

9、线程如何从可运行状态切换到终止状态

  1. 执行完毕
  2. 异常退出

10、Object.notify()、Object.notifyAll()之间的区别

Object.notify(): 随机唤醒一个wait线程,调用该方法后只有一个线程会由等待池进入锁池
Object.notifyAll(): 会将对象等待池中的所有线程都进入锁池,进行竞争。竞争到的线程会继续执行,在释放掉对象锁后,锁池中的线程会继续开始竞争。(进入到锁池的线程,不会再进入等待池)

11、等待池和锁池是什么?

  1. 等待池:线程调用对象的wait方法后,会释放该对象的锁,然后进入到该对象的等待池中
  2. 锁池:线程想要获得对象的锁,但是此时锁已经被其他线程拥有,这些线程就会进入到该对象的锁池中。

12、创建线程的三种方法

  1. 继承Thread,重写run方法
  2. 实现Runnable接口,并实现该接口的run方法
  3. 实现Callable接口(Executor框架中的内容,优势是能在任务结束后提供一个返回值,Runnable无法这样做),重写call方法。
  4. 推荐第二种Runnable接口的方法,因为继承Thread没有必要。

13、终止线程的两种方法

  1. 调用Thread.interrupted()设置中断标志位,并通过Thread.currentThread().isInterrupted()检查标志位。缺点:被中断的线程不一定会终止
  2. run()方法中设置boolean标志位(需要volatile修饰为易变变量):条件满足时跳出循环,run运行结束,thread安全终止

同步

14、重入锁是什么?(3)

  1. 重入锁ReentrantLock在Java SE 5.0引入
  2. 该锁支持一个线程对资源的重复加锁
  3. 一个线程在锁住锁对象后,其他任何线程都无法进入Lock语句
  val mLock = ReentrantLock()
  mLock.lock()
  try {
      //需要同步的操作
  }finally {
      mLock.unlock() //finally中进行解锁,避免死锁问题
  }

15、可重入锁的用途?

  1. 阻塞队列就是使用ReentrantLock实现的。

16、条件对象/条件变量的作用(4)

  1. 用于管理那些获得锁却因部分条件不满足而无法正常工作的线程
  2. 可以通过newCondition获得锁lock的条件变量(和ReentrantLock配合使用
  3. 条件对象调用await方法,当前线程就会阻塞并且放弃该锁
  4. await线程会进入阻塞状态,直到另一个线程,调用同一条件对象的signalAll()方法,之后等待的所有线程通过竞争条件去抢锁
        //1. 可重入锁
            val mLock = ReentrantLock()
            mLock.lock()
        //2. 条件变量
            val condition = mLock.newCondition()
            try {
                while(条件不满足){
        //3. await进入Block状态
                    condition.await()
                }
        //4. 条件满足方会进行后续操作
                        //...
        //5. 操作完成后调用同一条件变量的signalAll去激活等待该条件的线程
                condition.signalAll()
            }finally {
                mLock.unlock() //finally中进行解锁,避免死锁问题
            }

17、synchronized同步方法(4)

  1. Lockcondition提供了高度的锁定控制,然而大多数情况下不需要这样麻烦
  2. 从Java 1.0开始,每个对象都有一个内部锁
  3. 当一个方法使用synchronized修饰,意味着线程必须获得内部锁,才能调用该方法
synchronized public void doSth() throws InterruptedException{
    //1. 条件不满足,进入Block状态
    while(条件不满足){
        wait();
    }
    //2. 条件满足方会进行后续操作
        //...
    //3. 解除该锁,并通知所有阻塞的线程
    notifyAll();
}
  1. 备注:Kotlin学的不深,暂时没找到Kotlin中同步的方法,就用Java实现

18、同步代码块的使用(1)和问题(2)

  1. java中可以通过给一个Object对象上锁,来使用代码块
  2. 同步代码块非常脆弱不推荐
  3. 一般实现同步,最好使用java.util.concurrent包下提供的类,例如阻塞队列
Object object = new Object();
synchronized (object){
    //进行处理, 不推荐使用
}

19、synchronized方法和synchronized同步代码块的区别?

  1. 用synchronized修饰符修饰的方法就是同步方法
  2. synchronized代码块需要一个对象锁

20、死锁是什么?

死锁是指两个或者两个以上线程/进程进行资源竞争时出现了阻塞现象,如果没有外力帮助,它们都将无法继续工作下去,此时系统处于死锁状态

21、可能出现死锁的场景

  1. 可重入锁ReentrantLockmLock.lock()后如果出现异常,但是没有在try-catchfinally中没有执行mLock.unLock()就会导致死锁。
  2. notifynotifyAll更容易出现死锁

Java中的volatile

22、Java的堆内存是什么?(2)

  1. 堆内存用于存储对象实例
  2. 堆内存被所有线程所共享: 会存在内存可见性问题

23、 Java中的局部变量、方法定义的参数是否会被线程所共享?

  1. 局部变量、方法定义的参数则不会在线程间共享:不存在内存可见性问题,也不会受到内存模型影响,

24、Java内存模型的作用

  1. Java内存模型控制线程之间的通信,决定了一个线程对共享内存的写入何时对另一个线程可见。
  2. 定义了线程和主存之间的抽象关系:
    1. 线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存中存储了该该线程贡献变量的副本,
    2. 本地内存是Java内存模型中的抽象概念,实际上并不存在,覆盖了缓存、写缓存区、寄存器等区域。

24、Java内存模型图

graph TD;
    threadA(线程A);
    threadB(线程B);
    threadAM(本地内存);
    threadBM(本地内存);
    threadAV(共享变量副本A);
    threadBV(共享变量副本B);
    mainM(主内存_共享变量);
    threadA-->threadAM;
    threadAM-->threadAV;
    threadAV-->mainM;
    threadB-->threadBM;
    threadBM-->threadBV;
    threadBV-->mainM;

25、线程间共享变量的通信需要满足的步骤?(2)

A、B线程之间数据通信,需要满足两个步骤:
1. 第一步线程A将本地更新过的共享数据刷新到主存中;
2. 第二步线程B到主存中读取已经刷新的最新共享数据

26、可见性是什么?

  1. 可见性是指线程修改的状态能否立即对另一个线程可见
  2. volatile修饰的变量,在发生变化后会立即更新到主存,已保证共享数据的可见性

27、volatile关键字的作用

  1. 能保证有序性:禁止指令重新排序,之前的指令不会在volatile之后执行,之后指令也不会在之前执行
  2. 保证可见性:更新的数据立即可见
  3. 不保证原子性

28、有序性是什么?(2)

  1. Java中编译器和处理器能对指令的顺序重新排序,可不会影响单个线程执行的正确性,却无法保证多线程并发的正确性。
  2. 保证多线程并发的正确性就需要保证有序性

29、如何能保证有序性?(2)

1.volatile能保证有序性
2. synchronized和Lock也能保证有序性

30、原子性是什么?

  1. 对基本数据类型变量的赋值和读取是原子性操作-要么不执行,要么不会被中断。
  x = 3; //原子操作
  y = x; //非原子操作:复制,并且存储
  x++;  //非原子操作: 读取x,自加,存储

31、如何保证操作的原子性?

  1. java,util.concurrent.atomic包中很多类使用高效的机器级别指令来保证操作的原子性
  2. AtomicIntegerincrementAndGet/decrementAndGet()提供原子性自加/自减-可以作为共享计数器而无需同步
  3. AtomicBoolean、AtomicLong、AtomicReference等类也都是原子性操作
  4. 原子性操作类应该由开发并发工具的程序员使用,而不是应用程序员使用

32、使用volatile的典型场景

  1. 状态标志:如线程run方法中通过标志位判断是否终止线程,就比使用synchonized要简单和高效(通过synchronized也可以实现,但是该场景使用volatile性能更高)
  2. 双重检查模式(DCL): 应用于单例模式的getInstance保证实例唯一。DLC资源利用率高,第一次加载反应稍慢,在高并发情况下有一定缺陷

33、部分场景下使用volatile取代synchronized的要点

  1. synchronized能提供同步保护,却会影响性能。一定场景下可以用volatile替换。
  2. volatile无法保证原子性,必须具备两个条件才可以替换
  3. 条件1:对变量的写操作不依赖于当前值(不能自增、自减)
  4. 条件2:该变量没有包含在具有其他变量的不等式中(例如,volatile a,b, 不等式a

阻塞队列

34、什么是阻塞队列

  1. 阻塞队列常应用于生产者-消费者模型
  2. 阻塞队列需要满足1:队列中没有数据时,消费者端的所有线程全部自动阻塞(挂起)
  3. 阻塞队列需要满足2:队列中填满数据时,生产者端的所有线程都自动阻塞

35、阻塞队列(BlockingQueue)核心方法

  1. 放入数据:
    1. offer(object),可以存入,返回true;不可以存入,返回false。该方法不会阻塞当前线程
    2. put(Object), 阻塞队列有空间,则存入;没有空间,当前线程阻塞,直至阻塞队列有空间存放数据。
  2. 获取数据:
    1. poll(time):从阻塞队列中将首位对象取出。若不能取出,再等待time的时间。取不到就返回null
    2. take():取走队列中首位数据。若队列为空,则当前线程阻塞,直到队列中有数据,并且返回该数据。
    3. drainTo(): 一次性取走所有可用数据。无需多次加锁操作,提高效率。

36、Java中7种阻塞队列的要点

种类 特点 备注
ArrayBlockingQueue 数组组成的有界阻塞队列 默认不保证线程公平地访问队列
LinkedBlockingQueue 链表组成的有界阻塞队列 若构造时不指定队列缓存区大小,默认无穷大。一旦生产速度>消费速度,会导致内存耗尽
PriorityBlockingQueue 支持优先级排序的无界阻塞队列—默认升序排列 能通过compareTo方法和构造参数comparator对元素排序,但无法保证同级元素的顺序
DelayQueue 延时获取元素的无界阻塞队列 每个元素必须实现Delayed接口,创建时指定元素到期时间,元素到期后才能取出
SynchronousQueue 不存储元素的阻塞队列
LinkedTransferQueue 链表存储的无界阻塞队列
LinkedBlockingDeque 链表存储的双向阻塞队列 可以从两端同时插入和删除,减少一半竞争

37、阻塞队列ArrayBlockingQueue实现原理

  1. 内部维护一个Object类型的数组
  2. lock所采用的可重入锁(ReentrantLock)

38、ArrayBlockingQueue的put()源码解析和要点

/**
 *  存放数据
 *    1-有空间存放,就直接存入数据
 *    2-没有空间存放,当前线程阻塞到有多余空间,再存入
 */
public void put(E e) throws InterruptedException {
    Objects.requireNonNull(e);
    final ReentrantLock lock 
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值