java面试基础篇(线程池相关问题、死锁问题)

面试专题-java基础篇

1. 什么是面向对象?谈谈你对面试对象的理解

对比面向过程,是两种不同的处理问题的角度

面向过程更注重事情的每一个步骤及 顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么

比如:洗衣机洗衣服

面向过程会将任务拆解成一系列的步骤 (函数),1 打开洗衣机—>2 放衣---->放洗衣粉---->清洗----->烘干

面向对象会拆出人和洗衣机两个对象: 人:打开洗衣机放衣服放洗衣粉 洗衣机:清洗烘干

从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护

面向对象三大特性

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项 内部细节对外部调用透明,外部调用无需修改或者关心内部实现

1、javabean的属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定。而不能由外部胡乱修改

该name有自己的命名规则,明显不能由外部直接赋值

2、ORM框架

操作数据库,我们不需要关心链接是如何建立的、Sq|是如何执行的,只需要引入mybatis,调方法即可

继承:继承基类的方法,并做出自己的改变和/或扩展

子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的

多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。 父类引用指向子类对象,同时父类无法调用子类特有功能

2. 深拷贝与浅拷贝的理解

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。

浅拷贝是指,在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对引用类型的成员变量只进行引用的传递,并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。

深拷贝是指,在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量。

3. String、 StringBuffer StringBuilder的区别

  1. String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBuffer和StringBuilder是可变的

  2. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高

4. 接口与抽象类有什么区别

  1. 定义
    • 接口:接口是一种抽象数据类型,它只定义了一组方法的签名(即方法名称、返回类型和参数列表),但没有方法的实现。接口中的方法默认为抽象方法,不包含具体的代码。
    • 抽象类:抽象类是一个类,可以包含抽象方法(即没有方法体的方法),同时也可以包含具体方法。抽象类可以有实例变量,并且可以被继承。
  2. 多继承
    • 接口:Java支持多继承接口,一个类可以实现多个接口。这使得类能够获得多个不同接口的方法。
    • 抽象类:Java不支持多重继承的类,一个类只能继承一个抽象类。
  3. 构造方法
    • 接口:接口不能包含构造方法,因为它们不能实例化。
    • 抽象类:抽象类可以包含构造方法,可以用于初始化对象状态。
  4. 字段(属性)
    • 接口:接口中的字段默认为常量(public static final),必须初始化,并且只能包含常量。
    • 抽象类:抽象类可以包含实例字段,可以有不同的访问修饰符,可以被子类继承。
  5. 实现
    • 接口:类通过实现接口来获得接口中定义的方法。类必须提供方法的实现。
    • 抽象类:子类通过继承抽象类来获得方法。子类可以选择实现或覆盖抽象方法。
  6. 用途
    • 接口:适用于定义契约,强制实现多态性,允许类遵循多个不同的契约。
    • 抽象类:适用于定义类的基本结构,提供一些通用的方法和数据,允许代码重用。

5. wait与sleep有什么区别?

一个共同点,三个不同点

共同点

wait() 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

方法归属不同

sleep(long) 是 Thread 的静态方法 wait()都是 Object 的成员方法,每个对象都有

醒来时机不同

执行 sleep(long) 的线程会在等待相应毫秒后醒来 wait() 可以被 notify 唤醒,wait() 如果不唤醒就一直等下去

它们都可以被打断唤醒

锁特性不同(重点)

wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制

wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)

而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

6. 线程池相关问题

6.1. 对线程池的理解

线程池是一种池化技术,其实是一种资源复用思想的利用 常见的比如像线程池,连接池 内存池 对象池等这些

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最 大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

主要特点:线程复用;控制最大并发数:管理线程。

第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控

6.2. 线程池底层工作原理

在这里插入图片描述

第一步:线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会创建线程。也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创建corePoolSize个线程

第二步:调用execute()提交一个任务时,如果当前的工作线程数<corePoolSize,直接创建新的线程执行这个任务

第三步:如果当时工作线程数量>=corePoolSize,会将任务放入任务队列中缓存

第四步:如果队列已满,并且线程池中工作线程的数量<maximumPoolSize,还是会创建线程执行这个任务

第五步:如果队列已满,并且线程池中的线程已达到maximumPoolSize,这个时候会执行拒绝策略,JAVA线程池默认的策略是AbortPolicy,即抛出RejectedExecutionException异常

6.3. 如何自定义线程池参数

corePoolSize核心线程数目 - 池中会保留的最多线程数
maximumPoolSize最大线程数目 - 核心线程+救急线程的最大数
keepAliveTime生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
unit时间单位 - 救急线程的生存时间单位,如秒、毫秒等
workQueue当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
threadFactory线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
handler拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

6.4. 阻塞队列

常用的实现类

LinkedBlackingQueue 可以利用碎片化空间提升内存的利用率

ArrayBlackingQueue 空间连续

该参数是实际开发中重点需要调优和动态调整的参数, 队列的大小可以由项目所占内存大小和对应接口的吞吐量决定

6.5. 如何配置合理的线程数

CPU密集型

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。

CPU密集型任务配置尽可能少的线程数量:一般公式:CPU核数+1个线程的线程池

IO 密集型

由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2 I0密集型,即该任务需要大量的I0,即大量的阻塞。在单线程上运行I0密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

I0密集型时,cpu核数 / (1-0.9),核数为4的话,一般设置 40

6.6. 线程池拒绝策略有哪些?

拒绝策略分类含义使用场景
AbortPolicy丢弃任务并抛出异常 RejectedExecutionException我们项目中关于线程池的定义,使用的就是默认的如果这种需求是关键的业务,eg:商品详情/购物车/首页
DiscardPolicy安静的丢弃任务但是不抛出异常设计的时候,一些无关紧要的业务可以采用此策略Eg:单纯的展示某一项数据的情况 文章的浏览量/点赞个数
DiscardOldestPolicy丢弃队列最前面的任务,然后重新提交被拒绝的任务喜新厌旧使用场景不多,可根据特定场景使用
CallerRunsPolicy由调用线程处理该任务使用场景非常少

7. synchronized和Lock锁

在这里插入图片描述

8. 死锁问题

8.1 什么是死锁?

死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

8.2. 诱发死锁产生的原因有哪些?

​ 互斥条件
​ 占有且等待
​ 不可抢占
​ 循环等待

8.3 遇到死锁问题如何解决?

死锁一旦发生,其实基本上就很难人为干预解决他,所有我们只能尽可能的规避他 上面提到的四个条件,只要同时满足
了就会触发死锁 所有我们只需打破其中任意一条,死锁自然也就不会存在了 举例说明:
第一条: 互斥条件 这个作为锁所必须的条件,无法干预
第二条: 可以一次性申请所需的所有资源 此时就不存在等待的问题
第三条: 当其中一个线程再去申请资源的时候,如果申请不到,

死锁一旦发生,其实基本上就很难人为干预解决他,所有我们只能尽可能的规避他 上面提到的四个条件,只要同时满足
了就会触发死锁 所有我们只需打破其中任意一条,死锁自然也就不会存在了 举例说明:
第一条: 互斥条件 这个作为锁所必须的条件,无法干预
第二条: 可以一次性申请所需的所有资源 此时就不存在等待的问题
第三条: 当其中一个线程再去申请资源的时候,如果申请不到,
第四条: 给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。采用层次分配策略,将系统中所有的资源排列到不同层次中

  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值