1. Java内存模型?
image.jpeg
每个“共享变量(static变量,实例等)”都放在“主存”中,每个线程都有自己的“工作内存”,存放的是共享变量的“副本”;子线程只操作这个副本,修改完成后再将变量值写入主存中;
2. 内存间的交互操作?(8种原子操作,操作不可划分)
lock / unlock: 变量被单个线程“占有/释放”;
read / load / use: 从主存中“读取/导入工作内存/使用”;
assign / store / write: 工作内存中的副本“赋值/存储/写入主存”;
3. 原子性、可见性、有序性?
- 原子性:一个操作是“不可中断的”;要么操作成功,要么操作失败;eg: int a = 0; 属于原子操作(赋值);而 a++; int b = a; 都不是原子操作;(包括了“读取/写入/赋值”3个操作);
如何保证原子性?
synchronized/原子类/Lock类;(volatile不能保证原子性,只能保证可见性)
-
可见性:对于多线程访问同一个变量,当一个线程修改了变量值,其他线程都能立刻看到这个修改的值;(在读和写操作时,强制将“最新的”值更新到主存和强制从主存中获得最新的值)
-
有序性:在本线程内观察执行的语句,是“按顺序执行”的;在另一个线程内观察,没有逻辑关联的语句是乱序执行的(Jvm编译优化、指令重排);对于volatile修饰的变量,其操作指令禁止重排,保证有序性;
4. 线程调度的两种方式?
- 抢占式:由CPU轮转调度,对于线程本身,可以主动释放CPU资源,但不能主动获得CPU资源;
- 协同式:线程的执行时间和顺序由自己定义,执行完成后通知系统切换线程;
5. 实现线程安全的方法?
- 互斥同步:使用synchronized或Lock类;变量被当前线程占有时,其他线程阻塞,等待拿到这个对象的对象锁;
RentrantLock特点:比synchronized更加灵活、锁的粒度更小;支持可中断锁(针对长时间阻塞的情况)、公平锁(按申请锁的顺序获取锁);可以同时绑定多个条件,而synchronized只能用“嵌套”方式;
-
无阻塞同步:未解决线程频繁“阻塞/唤醒”的开销,采取乐观锁的思想,使用CAS算法,先尝试修改目的地址的对象,与期望值比较,若相等则认为对象没有被“修改过”;若修改失败则会不断尝试;
-
无同步方案:
(1) 可重入代码:即从当前代码位置跳转到其他线程,返回时,接着执行不会引起“线程安全”问题;
(2) 为线程提供共享变量的私有变量,在本线程内操作而不影响其他线程,并不是为了解决线程同步问题;ThreadLocal对象,绑定一个共享变量,维护一张ThreadLocal表;
6. 锁优化?
-
自旋锁:对于多CPU的机器,为了避免频繁“阻塞/唤醒”的开销,让需要被阻塞的线程不要立刻“放弃CPU时间”而是进入一个“忙循环”,看占有着这个目标对象的线程能不能很快释放对象锁;
-
锁消除:不存在线程共享变量就不需要加锁;
-
锁粗化:若一块代码内,多次“零散的”对同一个对象加锁,则直接对整个代码块加锁;
-
轻量级锁:尽量使用CAS操作,原子类;
-
偏向锁:对象锁会偏向的被“最先获取该锁”的线程获取;若该对象锁没有被其他线程获取,则这个持有对象锁的线程是不需要进行“变量同步”的;
7. 逃逸分析?
分为方法逃逸和线程逃逸;
- 方法逃逸:指在方法内定义的变量被方法外的引用访问;
- 线程逃逸:即“共享变量”,线程内定义的变量被其他线程访问;
如果一个变量不会发生线程逃逸,则可以做以下优化:
- 栈上分配:随方法的执行结束(入栈出栈)而销毁;
- 同步消除:对象逃逸至线程外,则不需要考虑同步问题;
- 标量替换:如果只是用到变量的某一个属性,则用这个属性(标量)替换整个对象(聚合量);