Bootstrap

并发编程面试题四

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:丢弃队列中最旧的任务,然后尝试重新提交当前任务。

 

;