Android面试---多线程

Android面试—多线程基础

个人语录:时间不负有心人,星光不问赶路人,以顶级好的态度写一篇博客

ps:这是大厂的经典问题,大家可以拿着直接背诵,加油,打工人 👨‍🏭,带着BAT大厂的面试问题去理解

去打工的欢快背影 - 打工人表情包合集_斗图表情

多线程的出现是要解决什么问题的?

答:CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异。

  • CPU 增加了缓存,以均衡与内存的速度差异;// 导致 可见性问题
  • 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致 原子性问题
  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致 有序性问题

线程不安全是指什么?

如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。

并发出现线程不安全的本质什么?

  • CPU缓存引起的可见性问题

  • 分时复用引起的原子性问题

  • 指令重排引起的有序性问题

    1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
    2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
    3. 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

Java是怎么解决并发问题的?

两个维度:

1、volatile、synchronized关键词和Happens-Before 规则以及as-if-serial原则

2、可见性,有序性,原子性

ps:个人觉得这两个都可以结合来讲述

  1. synchronized与原子性
  • 原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
  • 通过 monitorenter 和 monitorexit 指令,可以保证被 synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。线程一在执行monitorenter指令的时候,会对Monitor进行加锁,加锁后其他线程无法获得锁,除非线程一主动解锁。即使在执行过程中,由于某种原因,比如CPU时间片用完,线程一放弃了CPU,但是,他并没有进行解锁,而由于 synchorized 的锁是可以重入的,下一个时间片还是只能被他自己获取到,还是会继续执行代码,直到所有代码执行完,这就保证了原子性。

2.synchroized与可见性

  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 被 synchronized 修饰的代码,在开始执行时会加锁,执行完成后会进行解锁。对一个变量解锁之前,必须先把变量同步到主内存中。这样解锁后,后续线程就可以访问到被修改后的值。

3.synchronized与有序性

  • 有序性即程序执行的顺序按照代码的先后顺序执行。
  • 由于处理器优化和指令重排,CPU还可能对输入代码进行乱序执行,这就可能存在有序性问题
  • as-if-serial 语义规定:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变,编译器和处理器无论如何优化,都必须遵守 as-if-serial语义。as-if-serial语义保证了单线程中,指令重排是有一定限制的,而只要编译器和处理器都遵守这个语义,那么就可以认为单线程程序是按照顺序执行的

4.volatile与可见性

  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程也能够立即看到修改的值。
  • 被修饰的变量在被修改后可以立即同步到主存中,被其修饰的变量在每次使用之前都是从主内存中刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

5.volatile与有序性
volatile禁止指令重排优化,这就保证了代码的程序会严格按照代码的先后顺序执行,这就保证了有序性。

6.Happens-Before规则

让一个操作无需控制就能先于另一个操作完成

  • 单一线程原则:在一个线程内,在程序前面的操作先行发生于后面的操作。
  • 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
  • 线程启动规则:Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
  • 线程加入原则:Thread 对象的结束先行发生于 join() 方法返回
  • 线程中断原则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
  • 对象终结原则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
  • 传递性:如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

线程安全实现方法

  1. 互斥同步
    • synchronized
    • ReentrantLock
  2. 非阻塞同步
    • CAS
    • AtomicXXX类
  3. 无同步方案
    • 栈封闭
    • 线程本地存储(Thread Local Storage)
    • 可重入代码(Reentrant Code)

线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?

image

直接看图

New(新建)

Runnable(可运行)

Blocking(阻塞)

TimeWaiting(限期等待)

Waiting(无限期等待)

Terminated(死亡)

通常线程有哪几种使用方式?

  1. 实现Runnable接口
  2. 实现Callable接口
  3. 继承Thread类

线程的互斥同步方式有哪些? 如何比较和选择

1. 锁的实现

synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

3. 等待可中断

当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。

ReentrantLock 可中断,而 synchronized 不行。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 可以同时绑定多个 Condition 对象。

6.使用选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Liknana

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

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

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

打赏作者

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

抵扣说明:

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

余额充值