170 lines
8.4 KiB
Markdown
170 lines
8.4 KiB
Markdown
创建线程命名有意义,方便回溯
|
||
不通过 Executor 方法创建,使用 ThreadPoolExecutor 方式
|
||
必须回收自定义的 ThreadLocal 变量,尤其在线程池场景
|
||
高并发时,同步调用应该去考虑锁的性能损耗
|
||
能无锁就不要用锁
|
||
能锁区块就不要锁方法
|
||
能用对象锁就不要用类锁
|
||
使用阻塞获取等待锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁
|
||
try 中没有加锁 lock. lock ()就解锁 lock. unlock ()会抛出 IllegalMonitorStateException 异常
|
||
try 和加锁之间有代码报错,就不会执行 finally 方法报错释放锁
|
||
分布式锁优先使用 redis 锁,而不是数据库锁
|
||
|
||
## 线程池中提交一个任务的流程是怎样的?
|
||
|
||
使用 executor() 来提交一个工作线程,submit 会放在一个 Future 里面,返回 future
|
||
提交之后就判断当前worker是都达到核心线程数最大值,如果未达到,就创建线程并执行任务
|
||
如果达到了核心线程最大数,就将该任务放入队列中
|
||
如果队列已满,就 addWorker 创建新线程处理任务,如果达到最大线程数,就执行拒绝策略,默认为抛出异常
|
||
|
||
## 线程池中有几种状态?分别是如何变化的?
|
||
|
||
ctl 变量,AtomicInteger,表示状态
|
||
RUNNING,会接受新提交的任务
|
||
SHUTDOWN,shutdown ()不再接受新的任务,已有任务会执行完成
|
||
STOP,shutdownNow ()不接受新的任务,已有任务也会中止
|
||
先修改状态,避免新任务进来
|
||
TYDING,所有线程都终止后,会进入该状态,等待terminated
|
||
TERMINATED,terminated ()方法执行后进入该状态
|
||
|
||
## 如何优雅的停止一个线程
|
||
|
||
开启线程,execute (),无需返回值,submit (),需要有返回值,通过 Future. get ()获取
|
||
Thread t1 = new Thread()
|
||
t1.stop(), 该方法会直接停止,已经废弃,stop 方法会释放 sync 锁,ReentrantLock 不会释放
|
||
使用 interrupt 方法,Thread. getCurrent. isInterrupted 可以接受到中断的信号,但是线程自己控制是否停止, 睡眠时被中断会抛出InterruptedException
|
||
|
||
## 线程池的核心线程数、最大线程数该如何设置?
|
||
|
||
CPU 密集型任务,线程计算任务多,核心线程数+1,避免线程切换时间
|
||
IO 密集型任务,CPU 利用率不高,线程等待时间长,核心线程数 x2,避免 CPU 闲置
|
||
线程数=CPU 核心数*(1+等待时间/运行总时间)
|
||
|
||
## 如何理解 Java 并发中的可见性?
|
||
|
||
多线程修改变量时,一个线程修改了一个变量,然后其他线程可以立刻读取到这个变化的值修改时,如果在缓存就不能被其他线程读取到,使用 volatile 关键字确保读取从内存读取,写入同时写入缓存和内存
|
||
|
||
## 如何理解Java并发中的原子性?
|
||
|
||
CPU 按照时间片切换,读取值后被切换,其他线程修改了,但是切换回来不会再去读取该值,而是直接去修改该值,需要通过锁机制来保证原子性
|
||
|
||
## 如何理解 Java 并发中的有序性?
|
||
|
||
![[Pasted image 20240512214620.png]]
|
||
指令重排序,正常应该先初始化再返回地址
|
||
编译器优化,修改了执行顺序
|
||
通过锁和 volatile 关键字避免
|
||
|
||
## ForkJoinPool所解决的问题是什么
|
||
|
||
RecursiveTask,继承ForkJoinTask
|
||
当任务可以拆分的时候,就将任务拆分后分不同的线程处理,最后再将所有的计算结果累加
|
||
可以指定执行的线程数量,默认是机器的线程数量
|
||
|
||
## ThreadPoolExecutor的底层实现原理
|
||
|
||
addWorker 方法会新开线程
|
||
如果已经达到了最大核心线程,就会提交到队列中,有空闲的线程就会执行,队列已满会新开线程,直到最大线程数,超过队列就会拒绝新任务
|
||
|
||
## ForkJoinPool 拆分任务底层实现分析
|
||
|
||
新开一个线程会拆封任务,然后拆分两个线程 join 等待,然后继续拆分任务,直到达到最大线程数据设置
|
||
执行 compute 方法创建一个队列,把任务放进队列中,然后第一个线程取出该任务,将该大任务拆分,放入队列中,任务 1fork,创建一个自己的队列,将任务 1 取出来任务 2fork,任务 1join,任务 2join,之前的线程等待的时候会取帮助其他线程的任务
|
||
JDK 19 的虚拟线程也是基于ForkJoinPool
|
||
## ForkJoinPool的底层核心原理源码解析
|
||
|
||
## JDK19中虚拟线程基于ForkJoinPool的相关实现
|
||
|
||
## 如何理解 volatile 关键字
|
||
|
||
volatile 关键字用于避免指令重排序,同时保证共享变量在内存中的可见性,底层是使用内存屏障来实现的
|
||
|
||
## CountDownLatch 和 Semaphore 的区别和底层原理
|
||
|
||
countDownLatch 为控制线程等待,让一个线程等待倒计时结束后执行,通过 countDownLatch. awit 方法阻塞,直到为 0 后继续
|
||
semaphore,让线程通过 acquire 获取许可,获取不到被阻塞,通过 release 方法后返回许可
|
||
cycilcBarrier,当线程达到屏障时,等待一组线程
|
||
|
||
## ReentrantLock中的公平锁和非公平锁的底层实现
|
||
|
||
公平锁,去 AQS 排队获取锁
|
||
非公平锁,竞争锁,没有得到锁就去排队
|
||
这两种锁都是可重入锁,默认为非公平锁
|
||
|
||
## sleep、wait、join、yield
|
||
|
||
- 锁池
|
||
竞争锁的线程都会放入锁池中等待锁
|
||
|
||
- 等待池
|
||
调用 wait 方法会放入等待池中, 不会去竞争锁,只有 notify() 随机或 notifyAll () 会将所有线程放入锁池中
|
||
|
||
sleep 是 Thread 的静态方法,wait 是 object 方法
|
||
sleep 不会释放锁,释放 CPU 执行权,会冻结锁,wait 会释放锁,加入到等待队列中
|
||
sleep 不依赖 synchornized,wait 依赖 synchornized
|
||
sleep 不需要被唤醒,wait 需要
|
||
sleep 用于线程休眠或轮询暂停操作,wait 用于线程间通信
|
||
sleep 会让出 CPU 执行时间并强制上下文切换,wait 不一定,wait 后可能还是会有机会竞争到锁重新执行
|
||
|
||
yield 执行后线程进入就绪状态,释放了 cpu 执行权,但是保留了执行资格,还是可能下次线程调度继续执行
|
||
|
||
join 执行后线程阻塞,直到其他线程结束或中断线程
|
||
|
||
## Sychronized的偏向锁、轻量级锁、重量级锁
|
||
|
||
首先是无锁,然后当有线程获取锁的时候,会在对象头中记录下线程的 id,后面该线程来获取该锁的时候会直接获取锁,当有线程竞争之后,该锁升级成为轻量级锁,轻量级锁为自旋锁,自旋锁通过 CAS 获取预期的一个标记不会阻塞线程,当自旋过多时升级为重量级锁,重量级锁会阻塞线程
|
||
全局安全点执行清理任务的时候会尝试触发降级锁
|
||
|
||
## ThreadLocal 的底层原理
|
||
|
||
ThreadLocal 可以将数据缓存在一个线程内,每一个 Thread 对象都有一个 ThreadLocalMap,这个 Map 里面 key 为对应的 ThreadLocal,值为设置的缓存值,读取的时候以该 ThreadLocal 为引用,在自己的 map 里找对应的 key,
|
||
|
||
## ThreadLocal的原理的使用场景
|
||
|
||
数据库连接池,session 会话管理
|
||
连接管理,一个线程持有一个连接,该连接对象不在线程间共享
|
||
对象跨层传输时,避免多次传递
|
||
线程间数据隔离
|
||
事务操作时,存储线程事务信息
|
||
|
||
## ThreadLocal[[内存泄露]]问题,如何避免
|
||
|
||
线程和 ThreadLocalMap 是强引用,Map 和 ThreadLocal 的 Entity 也是强引用,Entry 的 key 是 ThreadLocal 对象,是一个[[弱引用]],没有指向 key 的强引用该 ThreadLocal 就会被垃圾回收器回收,使用完成之后线程没有回收,但是 Entry 中的 value 是强引用不会被回收,只有线程结束才会回收,value 对象过多的时候就会导致内存泄漏,但是只要调用 get 或 set 方法就可以清理掉需要使用之后手动调用 remove 方法来清除该值
|
||
|
||
## 阿里一面:如何查看线程死锁
|
||
|
||
## 阿里一面:线程之间如何进行通讯的
|
||
|
||
## 并发、并行、串行
|
||
|
||
## 并发篇1
|
||
|
||
## 并发篇2-1
|
||
|
||
## 并发篇2-2
|
||
|
||
## 并发篇3
|
||
|
||
## 并发4
|
||
|
||
## 并发5
|
||
|
||
## 并发三大特性
|
||
|
||
## 对线程安全的理解
|
||
|
||
## 京东一面:Java死锁如何避免
|
||
|
||
## 京东一面:如果你提交任务时,线程池队列已满,这时会发生什么
|
||
|
||
## 蚂蚁一面:sychronized的自旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系
|
||
|
||
## 说说你对守护线程的理解
|
||
|
||
## 为什么使用线程池,参数解释
|
||
|
||
## 线程的生命周期及状态
|
||
|
||
## 线程池线程复用的原理
|
||
|
||
## 线程池的底层工作原理 |