1、ReentrantLock和synchronized的区别及使用的场景
synchronized
关键字(是悲观锁):
- 自动管理:
synchronized
是 Java 提供的一种内置锁机制,使用简单,不需要显式地获取和释放锁。 - 可重入性:同一个线程可以多次获取同一个锁而不被阻塞。
- 不可中断:一旦一个线程开始等待获取锁,它不能被其他线程中断。
- 内存可见性:
synchronized
块或方法提供了内存可见性的保证,确保线程之间的共享变量更新是可见的。 - 公平性:默认情况下,
synchronized
不保证公平性(即先请求锁的线程不一定先获得锁)。
使用场景:对于大多数简单的同步需求,synchronized
是首选,因为它使用方便且不易出错。由于其可重入性,适合用于递归函数或嵌套方法调用中的同步。
ReentrantLock
类(Lock接口的实现类,是悲观锁):
- 显式管理:需要显式地调用
lock()
获取锁,并在完成后调用unlock()
释放锁。通常建议将unlock()
放在finally
块中以确保锁总是被释放。 - 可重入性:支持可重入锁,允许同一个线程多次获取同一个锁而不被阻塞。
- 可中断性:可以通过
lockInterruptibly()
方法尝试获取锁并在等待过程中响应中断。 - 公平性:可以通过构造函数指定是否启用公平锁,默认是非公平锁,传进参数true创建公平锁。
使用场景:在高竞争环境下,ReentrantLock
的性能通常优于 synchronized
,尤其是在启用了公平锁的情况下。
2、写出几条你遵循的多线程最佳实践
(1)、给不同模块的线程起名称,方便后续排查问题
(2)、使用同步代码块或者同步的方法的时候,尽量减小同步范围
(3)、多用并发集合少用同步集合
- 同步集合:Hashtable/Vector/同步工具类包装Collections.synXXX
- 并发集合:ConcurrentHashMap、CopyOnWriteArrayList
(4)、线上业务需要使用多线程,优先考虑线程池是否更加合适,然后判断哪种线程池比较好, 最后才是自己创建单一线程
3、线程池的好处以及常见的线程池有哪些
好处:可以复用存在的线程,减少线程创建和销毁的开销,可以控制并发数量、也可以定时定期执行。
常见的线程池:
newFixedThreadPool :一个定长线程池,可控制线程最大并发数,适用于需要限制并发线程数量的场景。
newCachedThreadPool :一个可缓存线程池,适用于执行大量短生命周期的任务的场景。
newSingleThreadExecutor :一个单线程化的线程池,用唯一的工作线程来执行任务,适用于需要顺序执行任务的场景。
newScheduledThreadPool :一个定长线程池,支持定时/周期性任务执行,适用于需要定期或延迟执行任务的场景。
这些线程池类型都是 Executors
类提供的多种静态工厂方法来创建不同类型的线程池,可以根据具体的业务需求选择合适的线程池类型。
4、为什么线程池不允许使用 Executors 去创建,要通过 ThreadPoolExecutor的方式
Executors创建的线程池底层也是调用 ThreadPoolExecutor,只不过使用不同的参数、队列、拒绝策略等,使用不当,会造成资源耗尽问题,直接使用ThreadPoolExecutor让使用者更加清楚线程池允许规则,常见参数的使用,避免风险。
常见的线程池问题:
(1)、newFixedThreadPool和newSingleThreadExecutor:
队列使用LinkedBlockingQueue,队列长度为 Integer.MAX_VALUE,可能造成堆积,导致OOM。
(2)、newScheduledThreadPool和newCachedThreadPool:
线程池里面允许最大的线程数是Integer.MAX_VALUE,可能会创建过多线程,导致OOM(内存溢出)。
5、分别解释一下ThreadPoolExecutor构造函数里面的各个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
(1)、corePoolSize:核心线程数,默认情况下核心线程会一直存活,即使没有任务也不会受存 keepAliveTime控制 。
(注意:在刚创建线程池时线程不会立即启动,到有任务提交时才开始创建线程并逐步线程数目达到corePoolSize。)
(2)、maximumPoolSize:线程池允许的最大线程数。当线程数达到这个值且任务队列已满时,新任务将根据拒绝策略处理。
(注意:当核心线程满,且任务队列也满时,才会判断当前线程数是否小于最大线程数,才决定是否创建新线程。)
(3)、keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收。
(4)、unit:指定keepAliveTime的单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS。
(5)、workQueue: 线程池中的任务队列,常用的是 ArrayBlockingQueue(需指定大小)、LinkedBlockingQueue(默认队列长度为 Integer.MAX_VALUE,最好指定大小)。
(6)、threadFactory:创建新线程时使用的工厂,一般使用默认的Executors.defaultThreadFactory()。
(7)、handler: RejectedExecutionHandler是一个接口且只有一个方法,线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略。
默认有4种策略AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
AbortPolicy
:抛出RejectedExecutionException
异常。CallerRunsPolicy
:由调用线程执行该任务。DiscardPolicy
:直接丢弃任务,不做任何处理。DiscardOldestPolicy
:丢弃队列中最旧的任务,然后尝试重新提交当前任务。