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、synchronizedReentrantLock

特性

synchronized

ReentrantLock

锁的实现

JVM 内置(基于对象头的 Mark Word 实现)

JDK 实现(基于 AbstractQueuedSynchronizer,AQS)

获取锁的方式

自动获取和释放(进入方法/代码块加锁,执行完毕自动释放)

手动获取和释放(必须调用 lock()unlock()

是否可中断

不可中断,除非抛出异常或线程终止

可中断,支持 lockInterruptibly()

是否支持尝试获取锁

不支持

支持 tryLock()(非阻塞获取锁)

是否支持超时获取锁

不支持

支持 tryLock(timeout, TimeUnit)

公平锁/非公平锁

只能是非公平锁

支持公平锁和非公平锁(默认非公平,构造时可指定)

锁的范围

代码块/方法级别

代码级别(可以更灵活地控制锁的范围)

性能优化

JDK 1.6 之后进行了优化(如偏向锁、轻量级锁、自旋锁)

适用于高并发场景,竞争激烈时比 synchronized更高效

条件变量(Condition)

不支持,必须用 wait()/ notify()

支持多个条件变量Condition

是否支持读写锁

不支持

需要使用 ReentrantReadWriteLock

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、灵活的任务管理:可支持灵活的任务队列,拒绝策略等
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐