Android面试—多线程基础
个人语录:
时间不负有心人,星光不问赶路人,以顶级好的态度写一篇博客
ps:这是大厂的经典问题,大家可以拿着直接背诵,加油,打工人 👨🏭,带着BAT大厂的面试问题去理解
![去打工的欢快背影 - 打工人表情包合集_斗图表情](http://tva3.sinaimg.cn/large/0068Lfdely1gjid8tfghhj30j60ioq3z.jpg)
文章目录
多线程的出现是要解决什么问题的?
答:CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异。
- CPU 增加了缓存,以均衡与内存的速度差异;// 导致
可见性
问题 - 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致
原子性
问题 - 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致
有序性
问题
线程不安全是指什么?
如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。
并发出现线程不安全的本质什么?
-
CPU缓存引起的可见性问题
-
分时复用引起的原子性问题
-
指令重排引起的有序性问题
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
Java是怎么解决并发问题的?
两个维度:
1、volatile、synchronized关键词和Happens-Before 规则以及as-if-serial原则
2、可见性,有序性,原子性
ps:个人觉得这两个都可以结合来讲述
- 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。
线程安全实现方法
- 互斥同步
- synchronized
- ReentrantLock
- 非阻塞同步
- CAS
- AtomicXXX类
- 无同步方案
- 栈封闭
- 线程本地存储(Thread Local Storage)
- 可重入代码(Reentrant Code)
线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
直接看图
New(新建)
Runnable(可运行)
Blocking(阻塞)
TimeWaiting(限期等待)
Waiting(无限期等待)
Terminated(死亡)
通常线程有哪几种使用方式?
- 实现Runnable接口
- 实现Callable接口
- 继承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 会确保锁的释放。