多线程面试一

目录

进程和线程的区别

线程的创建方式有几种及区别

线程池类结构

线程池参数

线程池拒绝策略

线程池工作流程


进程和线程的区别

定义: 进程: 进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己的独立内存空间,并且拥有独立的地址空间、数据段、堆栈段等。

线程: 线程是进程中的一个执行单元,是调度和执行的基本单位。同一进程中的多个线程共享进程的资源,包括内存空间、文件句柄等。

资源分配: 进程拥有独立的资源,比如内存空间、文件句柄等。 线程则共享进程的资源,除了CPU寄存器和栈之外,其他资源都是共享的。

上下文切换: 进程间的上下文切换涉及更多的系统资源,因此开销较大。 线程间的上下文切换只涉及少量寄存器和栈的保存与恢复,开销较小。

通信机制: 进程间通信(IPC)通常需要通过操作系统提供的机制,如管道、消息队列、信号量等。 线程间可以直接访问共享内存,通信更加直接和高效。

创建和销毁: 创建一个新的进程涉及加载新的地址空间,分配资源等,因此创建进程的开销较大。 创建线程的开销相对较小,因为不需要额外的地址空间。

并发执行: 在同一时刻,一个进程可以有多个线程并发执行。 线程的并发执行可以通过CPU的时间片轮转机制来实现。

隔离性: 进程之间是相互隔离的,每个进程有自己的地址空间,因此一个进程的崩溃不会影响到另一个进程。 线程在同一进程中共享资源,因此一个线程的错误可能会导致整个进程崩溃。

线程的创建方式有几种及区别

继承 Thread 类: 定义: 定义一个类继承 Thread 类,并重写 run() 方法。 优点: 简单易用,可以直接访问和设置线程属性。 缺点: 不能继承其他类,因为Java不支持多重继承。

实现 Runnable 接口: 定义: 定义一个类实现 Runnable 接口,并实现 run() 方法。 优点: 可以同时实现其他接口,灵活性较高。 缺点: 需要通过 Thread 构造器传入 Runnable 实例。

实现 Callable 接口: 定义: 定义一个类实现 Callable 接口,并实现 call() 方法。 优点: 支持返回值和抛出异常。 缺点: 需要使用 FutureTask 包装 Callable 对象。

使用线程池: 定义: 利用 ExecutorService 接口提供的线程池来管理线程。 优点: 可以复用线程减少创建和销毁线程的开销,适用于大量线程的情况。 缺点: 需要额外配置线程池。

使用 ThreadFactory: 定义: ThreadFactory 提供了一种创建线程的方法,通常与线程池一起使用。 优点: 可以通过 ThreadFactoryBuilder (Guava库) 或自定义实现来创建具有特定属性的线程。 缺点: 需要额外配置。

回答完总结 继承 Thread 类 和 实现 Runnable 接口 是最常用的创建线程的方式,适用于大多数情况。 实现 Callable 接口 适用于需要线程返回结果的场景。 使用线程池 适用于需要管理大量线程的应用,可以有效控制线程数量并复用线程。 使用 ThreadFactory 适用于需要定制线程属性的情况,通常与线程池一起使用。

线程池类结构

Executor 这是所有线程池的顶层接口,它仅定义了一个方法execute(Runnable command),用于执行给定的Runnable任务。但是,Executor本身并不提供线程池的功能,它只是一个简单的执行器。

ExecutorService 继承自Executor,ExecutorService提供了更丰富的线程管理和控制功能,包括启动、停止线程池,以及控制任务的执行。它定义了一些额外的方法,如submit()、invokeAll()、invokeAny()和shutdown()等。

AbstractExecutorService这是一个抽象类,实现了ExecutorService接口,提供了线程池的一些基础实现,比如shutdown()和isShutdown()方法。子类通常只需要实现特定的方法,如execute()。

ThreadPoolExecutor 这是线程池的核心实现类,继承自AbstractExecutorService。ThreadPoolExecutor提供了对线程池的详细控制,包括线程数量、队列类型、线程工厂、拒绝策略等。它是创建自定义线程池的主要类。

ScheduledExecutorService 这个接口也继承自ExecutorService,但它专注于定时任务的执行,允许以固定延迟或周期性地执行任务。

ScheduledThreadPoolExecutor 这是ScheduledExecutorService的一个实现,基于ThreadPoolExecutor,它支持定时和周期性的任务执行。

线程池参数

创建线程池时,通常需要指定以下参数:

corePoolSize  : 核心线程数,即线程池中的线程数目达到此数量后,新来的任务会被放入任务队列中等待执行。

maximumPoolSize: 最大线程数,当任务队列满时,线程池会继续创建新的线程来处理任务,直到达到这个最大值。

keepAliveTime: 当线程池中的线程数目超过核心线程数时,多余的空闲线程的存活时间。如果设置为0,则多余的线程会立即终止。

workQueue: 用于存放等待执行的任务的阻塞队列,常见的有ArrayBlockingQueue, LinkedBlockingQueue等。

threadFactory: 创建新线程的工厂,可以自定义线程的属性,如优先级、守护状态等。

handler: 拒绝策略,当任务队列和线程池都已满时,如何处理新来的任务。

线程池拒绝策略

线程池在无法接受更多任务时,会根据拒绝策略来处理新任务:

AbortPolicy: 抛出RejectedExecutionException异常,默认策略。

CallerRunsPolicy: 调用者所在的线程会执行该任务,可能会降低调用者的响应速度或吞吐量。

DiscardPolicy: 直接丢弃任务,不做任何处理也不抛出异常。

DiscardOldestPolicy: 丢弃队列里最近的一个任务,并尝试再次提交当前任务。

线程池工作流程

提交任务: 用户通过submit()或execute()方法向线程池提交任务。

分配任务: 如果当前运行的线程少于corePoolSize,即使线程池中有其他空闲的线程,也会创建一个新的线程来处理任务。

排队: 如果当前运行的线程等于corePoolSize,那么任务会被放入workQueue中等待执行。

扩展线程: 如果workQueue满了且当前线程数小于maximumPoolSize,则创建新的线程来处理任务。

拒绝任务: 如果当前线程数等于maximumPoolSize且workQueue也满了,那么根据handler策略处理任务。

回收线程: 当线程池中的线程数目大于corePoolSize时,超出的线程会在空闲keepAliveTime时间后被终止。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值