并发编程面试题
- 1.创建线程的方式有哪些?
- 2. 线程的 5 种状态及状态流转
- 3.线程中run方法和start方法区别?
- 4.【高频】线程池在业务中的使用场景
- 5.【高频】线程池有哪些参数
- 6.ThreadPoolExecutor饱和策略
- 7.线程池实现原理
- 8.项目中会存在一些并发问题吗?
- 9.接口会有一些并发处理机制
- 10.juc工具包,AQS,队列数据结构是怎么样的 链表在公平锁和非公平锁情况下的处理
- 11.synchronized和lock区别
- 12.synchronizedy优化过程,偏向锁怎么实现的?轻量级锁怎么实现的?
- 13.volatile在Double-check单例模式中的作用
- 24.ThreadLocal,使用场景,注意事项有哪些,ThreadLocal内存泄漏怎么解决
- 25.线程池参数
- 26.线程池内部
1.创建线程的方式有哪些?
创建线程有四种方式:
- 继承 Thread 类;
- 实现 Runnable 接口;
- 实现 Callable 接口;
- 使用 Executors 工具类创建线程池
- 使用 ThreadPoolExecutor自定义线程池【阿里规范自定义线程池】
1.1继承 Thread 类
- 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()方法就是线程要执行的业务逻辑方法
- 创建自定义的线程子类对象
- 调用子类实例的star()方法来启动线程
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("继承Thread线程");
myThread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
}
}
执行结果:
1.2实现 Runnable 接口
- 定义Runnable接口实现类MyRunnable,并重写run()方法
- 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.setName("实现runnable接口线程");
thread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
}
执行结果:
1.3实现 Callable 接口
- 创建实现Callable接口的类myCallable
- 创建FutureTask对象
- 创建Thread对象
- 调用线程对象的start()方法
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
return 1;
}
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.setName("实现Callable接口线程");
thread.start();
try {
Thread.sleep(1000);
System.out.println("返回结果 " + futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
}
执行结果:
1.4使用 Executors 工具类创建线程池
创建链接如下:Executors 创建线程池
总结:
(1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
(3) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
(4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
缺点:
newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1.5使用 ThreadPoolExecutor自定义线程池【阿里规范自定义线程池】
/**XXService*/
public interface XXService {
Long getRandomNumber(Long businessId);
}
/**XXService实现类*/
@Service
public class XXServiceImpl implements XXService{
@Override
public Long getRandomNumber(Long businessId) {
return new Random().nextInt(100) + businessId;
}
}
/**执行线程*/
public class TaskThread implements Callable<Object> {
private Long businessId;
private XXService xxService;
/**构造方法*/
public TaskThread(Long businessId, XXService xxService) {
this.businessId = businessId;
this.xxService = xxService;
}
@Override
public Long call() {
System.out.println("ThreadName" + Thread.currentThread().getName() + "执行业务逻辑");
//业务逻辑处理
Long randomNumber = xxService.getRandomNumber(businessId);
System.out.println("ThreadName" + Thread.currentThread().getName() + "获取返回结果为: " + randomNumber);
return randomNumber;
}
}
/**构造线程池*/
public class ResolveThreadExecutor {
public static <V, T extends Callable<V>> Set<V> executeCompletionService(List<T> tasks) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 40,
10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(tasks.size()),
getThreadFactory(), new CustomRejectedExecutionHandler()
);
Set<V> result = new HashSet<>(10);
CompletionService<V> completionService = new ExecutorCompletionService<>(executor);
List<Future<V>> futureList = new ArrayList<>();
for (T task : tasks) {
Future<V> future = completionService.submit(task);
futureList.add(future);
}
for (Future<V> future : futureList) {
try {
//方法是阻塞的,即:线程在没有返回结果前,get方法会一直等待,所以等线程执行完再获取结果
result.add(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
return result;
}
private static ThreadFactory getThreadFactory() {
return new ThreadFactory() {
//int i = 0; 用并发安全的包装类
AtomicInteger atomicInteger = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
//创建线程把任务传进来
Thread thread = new Thread(r);
//给线程起个名字
thread.setName("Content-Thread" + atomicInteger.getAndIncrement());
return thread;
}
};
}
private static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 核心改造点,由blockingqueue的offer改成put阻塞方法
// 对于put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞一直等待空间,以加入元素
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**业务执行调用*/
public class ThreadExecutorUsage {
public static void main(String[] args) {
List<TaskThread> taskList = new ArrayList<>();
for (int i = 0; i < 6; i++) {
//逻辑处理生成不同的TaskThread
taskList.add(new TaskThread((long) i, new XXServiceImpl()));
}
Set<Object> failNewsInfoIds = ResolveThreadExecutor.executeCompletionService(taskList);
System.out.println(failNewsInfoIds);
}
}
执行结果:
2. 线程的 5 种状态及状态流转
3.线程中run方法和start方法区别?
线程Thread类的start()方法和run()方法.
总结:
start方法用来启动线程,真正实现了多线程并发执行运行。start方法的作用就是将线程由NEW状态,变为RUNABLE状态。当线程创建成功时,线程处于NEW(新建)状态,如果你不调用start方法,那么线程永远处于NEW状态。调用start后,才会变为RUNABLE状态,线程要等待CPU调度,start方法的被调用顺序不能决定线程的执行顺序
run方法其实是一个普通方法,是不能实现并发的,线程对象可以随时随地调用run方法,只不过当线程调用了start方法后,一旦线程被CPU调度,处于运行状态,那么线程才会去调用这个run方法;
public class MyTask implements Runnable{
private Integer number;
public MyTask(Integer number){
this.number = number;
}
@Override
public void run() {
System.out.println(number);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask(1));
Thread t2 = new Thread(new MyTask(2));
//分开执行先执行t1、t2的start方法
//t1.start();
//t2.start();
//再执行t1、t2的run方法
t1.run();
t2.run();
}
执行结果对比:
4.【高频】线程池在业务中的使用场景
5.【高频】线程池有哪些参数
ThreadPoolExecutor 3 个最重要的参数:
corePoolSize :核心线程数,线程数定义了最小可以同时运行的线程数量。
maximumPoolSize :线程池中允许存在的工作线程的最大数量
workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在队列中。
ThreadPoolExecutor其他常见参数:
keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
TimeUnit :keepAliveTime 参数的时间单位。
ThreadFactory:为线程池提供创建新线程的线程工厂
RejectedExecutionHandler :线程池任务队列超过 maxinumPoolSize 之后的拒绝策略
6.ThreadPoolExecutor饱和策略
ThreadPoolExecutor 饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:
ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
举个例子: Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。
7.线程池实现原理
8.项目中会存在一些并发问题吗?
9.接口会有一些并发处理机制
10.juc工具包,AQS,队列数据结构是怎么样的 链表在公平锁和非公平锁情况下的处理
【深入AQS原理】我画了35张图就是为了让你深入 AQS
AQS详解
11.synchronized和lock区别
面试官:谈谈synchronized与ReentrantLock的区别?
12.synchronizedy优化过程,偏向锁怎么实现的?轻量级锁怎么实现的?
13.volatile在Double-check单例模式中的作用
24.ThreadLocal,使用场景,注意事项有哪些,ThreadLocal内存泄漏怎么解决
ThreadLocal全面解析
听说你看过ThreadLocal源码,来面试下这几个问题
谈谈引用和Threadlocal的那些事
面试官:听说你精通并发编程,来说说你对ThreadLocal的理解
ThreadLocal的内存泄露?什么原因?如何避免?