juc mian
·
1、指令重排序?
时机:
1、编译时
2、运行时
如何禁止:
1、volatile (内存屏障)
2、final
3、synchronized
ps: 重排序是为了提高性能,但会破坏多线程程序的可见性。
2、synchronized怎么实现的? ReadWriteLock?ReentrantLock?
3、synchronized 和 volatile
1、
synchronized:原子性、可见性、有序性
volatile:可见性、有序性
2、性能:synchronized < volatile
3、volatile不能替代synchronized,因为不保证原子性。
i++:内部过程是read - modify - write三步操作,多线程无法保障原子性
4、synchronized性能不一定就差(JDK1.6+):
无竞争时:偏向锁(几乎无开销)
轻度竞争:自旋锁(CAS)。一旦有2+线程CAS失败,就升级锁。
重度竞争:重量级锁(阻塞)。
5、公平锁和非公平锁?
公平锁:按线程排队的顺序执行
优点:1、避免饥饿线程 2、任务公平调度,保证顺序性
缺点:1、需要单独维护队列(排队机制)
2、一些快速任务,原本可以直接很快执行完成,但是排队的话可能要等其他执行时间久的任务
3、上下文切换,频繁的线程调度
非公平锁:抢占式的
优点:1、性能好 2、高吞吐
缺点:1、饥饿线程 2、无法保证任务顺序
6、synchronized的锁升级(JDK1.6后)?
1、锁的升级是单向的,从低到高,不可降级(除了偏向锁可以被撤销)
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
无锁 -> 偏向锁:(伪锁)
1、触发条件:当第一个线程访问同步块时
2、实现原理:
a、CAS操作将对象头的Mark Word字段指向锁记录的指针
b、如果成功,该线程获得偏向锁,并在对象头中记录线程ID
c、之后该线程进入同步块不需要任何同步操作(只需检查线程ID是否是自己)
3、优点:没有竞争时完全消除了同步开销
偏向锁 -> 轻量级锁:(伪锁)
1、触发条件:当另一个线程也访问此同步块的时候
2、实现原理:
a、撤销偏向锁(需要暂停持有偏向锁的线程)
b、通过 CAS 操作竞争将对象头 Mark Word 指向自己的锁记录
c、成功的线程获得轻量级锁,失败的线程自旋尝试获取锁(while)
3、优点:避免了线程阻塞,使用CAS自旋代替阻塞
轻量级锁 -> 重量级锁:
1、触发条件:当自旋超过一定次数(默认10次)或者等待线程超过1个
2、实现原理:
a、轻量级锁膨胀为重量级锁
b、对象头 Mark Word 指向操作系统级别的互斥量(mutex)
c、未获取锁的线程进入阻塞状态,被放入等待队列
3、优点:减少CPU空转,适用于高竞争场景
7、CAS?
CAS:compare and swap
1、要更新的变量
2、预期的值
3、要修改的值
如果实际的值和预期的值一样,则修改值
如果不一致,说明其他线程已经修改了,不做任何操作
应用场景:原子类、ConcurrentHashMap、自旋锁、乐观锁
优点:无锁操作,性能优。
缺点:1、ABA问题 2、长时间循环的话CPU消耗高
8、ABA问题?
100 -> 101 -> 100,普通的cas会认为没有修改过,但实际已经改动了
可以用版本号的概念解决:AtomicStampedReference
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp);
atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
atomicStampedRef.compareAndSet(101, 100, stamp+1, stamp+2);
9、临界区?
临界区:多个线程/进程访问共享资源的代码块,需要互斥保护
10、可中断锁
可中断锁:可中断锁允许线程在等待锁时被中断,从而避免死锁或长时间阻塞问题。
ReentrantLock.lockInterruptibly()
超时控制、避免死锁或任务取消
11、synchronized 和 ReentrantLock
|
特性 |
|
|
|
锁的实现 |
JVM 内置(基于对象头的 Mark Word 实现) |
JDK 实现(基于 |
|
获取锁的方式 |
自动获取和释放(进入方法/代码块加锁,执行完毕自动释放) |
手动获取和释放(必须调用 |
|
是否可中断 |
不可中断,除非抛出异常或线程终止 |
可中断,支持 |
|
是否支持尝试获取锁 |
不支持 |
支持 |
|
是否支持超时获取锁 |
不支持 |
支持 |
|
公平锁/非公平锁 |
只能是非公平锁 |
支持公平锁和非公平锁(默认非公平,构造时可指定) |
|
锁的范围 |
代码块/方法级别 |
代码级别(可以更灵活地控制锁的范围) |
|
性能优化 |
JDK 1.6 之后进行了优化(如偏向锁、轻量级锁、自旋锁) |
适用于高并发场景,竞争激烈时比 |
|
条件变量(Condition) |
不支持,必须用 |
支持多个条件变量( |
|
是否支持读写锁 |
不支持 |
需要使用 |
12、IO密集型任务和计算密集型任务
13、创建线程池的方式?
1、 Executors:
Executors.newFixedThreadPool(int nThreads); 固定大小线程池
Executors.newSingleThreadExecutor(); 单线程线程池
Executors.newCachedThreadPool(); 可缓存线程池
2、 ThreadPoolExecutor:
需自己填充线程池的七个参数
推荐使用ThreadPoolExecutor,因为Executors可能会有无界队列或最大线程数为int_max的情况
14、线程池的参数?
1、核心线程数
2、最大线程数
3、空闲线程存活时间
4、时间单位
5、工作队列
6、线程工厂:用于创建新线程的工厂
可以自定义线程名称、优先级、守护状态等
7、拒绝策略
a、直接丢弃,抛出异常
b、由提交任务的线程直接执行该任务
c、直接丢弃任务
d、丢弃队列中最老的任务,然后尝试重新提交当前任务
15、线程池的工作流程?
16、如何合理配置线程数大小?
CPU密集型(计算密集型):复杂计算,图像处理
线程数设置为CPU核数或者略大于CPU核数,略大于的线程是备用线程。
最大化利用CPU资源,避免过多的线程上下文切换开销
IO密集型 : 网络请求、数据库操作,大部分时间都在等待IO
一般设置为CPU核数的2~3倍
在某个线程等待IO时,其他线程可以使用CPU。但是线程切换开销增大,内存消耗大。
17、线程池关闭的方式?
shutdown():平缓关闭,不再接受新任务,但会处理完已提交的任务
shutdownNow():立即关闭,尝试停止所有正在执行的任务,返回等待执行的任务列表
18、线程池预热是为了什么
防止突发的高流量
19、创建线程的方式?
1、继承Thread
适合简单任务
单继承,继承后不能再继承其他类了
实现run方法
2、实现Runnable接口
推荐方式
可以实现多个接口,灵活
实现run方法
3、实现Callable接口
可以返回结果
可以抛出异常
实现call方法
4、开一个线程池
5、定时任务线程 (网安)
20、run 和 start

21、Lock和synchronized
聊到 synchronized 和 ReentrantLock
22、wait和sleep有什么区别
23、简述 Java 中线程的生命周期和创建线程的方式。
24、为什么推荐使用线程池而不是直接使用线程
1、资源再利用
2、避免过多的线程销毁和创建
3、线程池会自动管理线程的销毁,无需手动关闭造成线程泄露
4、灵活的任务管理:可支持灵活的任务队列,拒绝策略等更多推荐



所有评论(0)