关于本人java面试实际碰到的问题记录 第一章初稿

目录

基础

网站是如何访问的?

String、StringBuffer、StringBuilder  --- 这个刚毕业那会面试官好像特喜欢问这个

微服务和分布式的区别

SpringCloud的优缺点

你使用过哪些springCloud组件?

负载均衡有哪些策略?

断路器解决了什么问题?

反射

接口和抽象类的区别

Java中的四种引用类型

final finally 和finalize的区别

static

使用 Spring Cloud 有什么优势?

为什么重写 equals() 就一定要重写 hashCode() 方法

Java创建对象的方式

序列化和反序列化

Object类有哪些常用方法

重写和重载

==和equals

int和Integer的区别

深拷贝和浅拷贝有什么区别        --- 这个问的也多

JDK和JRE的区别

maven的package、install、deploy命令的区别

双亲委派机制

哪些数据适合放入缓存

类加载

#{}和${}的区别是什么?

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

线程---重点

线程两次调用start会有问题吗?

线程有哪些基本状态(以及生命周期)? 

Java中实现多线程有几种方法?

volatile和synchronized有什么区别

synchronized和lock的区别

sleep和wait有什么区别

线程有哪些方法

守护线程

Executor线程中内部出现数据不一致怎么办

池化技术的作用

线程池(Java中有哪些方法获取多线程)  --- 重点

前言

Callable接口

线程池的好处

架构说明

工作原理

常用三种创建线程池方式

七大参数

拒绝策略

自定义线程池

线程池的合理参数设置


基础

网站是如何访问的?

1、输入域名,回车

2、先去本机C盘 System32 的drivers文件下检查有没有这个域名映射

有:返回对应的IP地址,并直接访问该IP地址

无:去DNS服务器找,找到就返回,找不到就返回找不到

String、StringBuffer、StringBuilder  --- 这个刚毕业那会面试官好像特喜欢问这个

String、StringBuffer都是线程安全的

String是不可变长度,StringBuffer、StringBuilder是可变长度 通过append()进行追加

String:final修饰,不可变长度

StringBuffer:方法有synchronized关键字,由于它加锁,会涉及资源竞争等待,所以速度比StringBuilder慢

微服务和分布式的区别

微服务:微服务将模块拆分成一个独立的服务单元,各个服务之间独立运行,从而避免因某个模块的升级和BUG影响现有的系统业务。

分布式:将一个大的系统划分为多个业务模块并部署在不同的机器上,提高计算速度。

两者都可以通过RPC进行调用。

SpringCloud的优缺点

优点:

1、服务独立部署

2、代码复用:每个服务提供 RestAPI 通过接口远程调用

3、服务快速启动,拆分后,依赖少了,启动变快

4、团队分供明确:每个团队只用负责自己的业务

缺点:

需要处理分布式缓存、分布式事务。

你使用过哪些springCloud组件?

1、服务的祖册发现:Eureka

2、负载均衡:Ribbon

3、网关:Zuul

4、分布式配置:config

5、断路器:Hystrix

负载均衡有哪些策略?

1、随机 2、轮询 3、ip (每个请求按访问ip的hash结果分配)

4、最少连接 (把请求转发给连接数较少的后端服务器) 5、响应时间

断路器解决了什么问题?

当某个服务出现故障时,故障不会沿着服务的调用链路在系统中蔓延,最终导致整个服务的瘫痪。

实现目标:

1、防止故障扩散:防止故障扩散到其他服务

2、提供降级:失败会调用兜底方法

3、保护线程资源:防止单个服务故障耗尽系统中所有的线程资源

反射

不修改源代码的情况下,可以动态获取到类实例

反射的实现方式

1、Class.forName("类的全路径");

2、类名.class

3、对象名.getClass

缺点:需要解析字节码,数据量大的时候,性能低

接口和抽象类的区别

相同:都不能被实例化

不同:

1、类是继承某个单个类,接口是可以实现多个个接口

2、抽象类可以有非抽象的方法,抽象类并不需要覆盖重写父类的非抽象的方法!

3、抽象类可以有构造器,接口不能有构造器 抽象类的构造器通过子类构造器中的supper(...);进行赋值

Java中的四种引用类型

强引用:内存不足也不会被回收

软引用:内存不足会被回收

弱引用:垃圾回收器发现就会被回收

虚引用:与弱引用类似

final finally 和finalize的区别

1、final
final修饰类 该类不可以被继承    
final修饰方法  该方法不可以被覆盖重写   
final修饰变量   该变量值不可被修改.
​
​
2、finally
不管有没有异常被抛出、捕获,finally块都会被执行。
try块中的内容是在无异常时执行到结束。
catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。
​
​
3、finalize是方法名
垃圾收集器在确定某个对象没有被引用时,调用该方法进行垃圾回收处理。
它是在object类中定义的,因此所有的类都继承了它。

拓展:

try里面有return,finally会执行吗?

会!finally的执行早于try里面的return,不管有没有异常,finally块中的代码都会执行

final 与 abstract不能同时使用

final修饰类,类不可以继承 abstract修饰类为抽象类,需要有实现

抽象类中的非抽象方法,可以不用在子类中重写

static

首先肯定:静态变量是被对象共享的 jdk8之前:放在方法区 jdk8及以后:存放在堆中反射的class对象(即类加载后会在堆中生成一个对应的class对象)的尾部

  1. 静态方法只能访问静态成员

  2. 非静态方法可以访问所有成员

静态代码块只执行一次,运行一开始就会开辟内存。

被static/transient修饰的成员变量,不能被序列化

静态变量和静态方法.也就是被static所修饰的变量/方法都属于【类的静态资源】,类实例所共享.除了静态变量和静态方法之外

static也用于静态块,多用于初始化操作。

此外static也多用于修饰内部类,此时称之为静态内部类.最后一种用法就是静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名。

static修饰的变量可以是线程安全的,也可以是非线程安全的,这取决于具体的实现方式和并发访问的场景。如果一个static变量被多个线程同时访问,而且没有采取任何线程同步机制(比如锁、原子操作等),那么它就是非线程安全的,可能会导致数据竞争和并发访问问题。但是,如果一个static变量被设计成只读(即不可变的),那么它就是线程安全的,因为它不会发生并发访问问题。
final修饰的变量是线程安全的,因为final变量在初始化之后就不能再被修改,不存在并发访问问题。final变量在多线程环境下可以被同时访问,不需要额外的线程同步机制。需要注意的是,如果final变量引用了一个可变对象,那么该对象本身并不是线程安全的,需要采取相应的线程同步措施来保证其线程安全。

使用 Spring Cloud 有什么优势?

1. 易于开发和维护:Spring Cloud采用了微服务架构,能够帮助开发人员快速构建、部署和维护跨多平台、多语言的微服务应用。同时,Spring Cloud还提供了丰富的组件和工具,使得开发人员能够更加灵活地选择所需的功能组件。
2. 高可用性:Spring Cloud可通过负载均衡和服务注册中心等功能,实现对微服务的自动化管理和扩展。这可以提高微服务的可用性和可扩展性,帮助应用系统保持高可用性。
3. 安全性:Spring Cloud提供了多种安全机制和技术,可以确保微服务之间的通信安全性和数据保护。同时,Spring Cloud还提供了身份验证、授权和凭证管理等功能,能够帮助开发人员保障应用系统的安全性。
4. 透明性:Spring Cloud的各个部件都具有高度的可扩展性和可配置性,能够帮助开发人员更好地实现应用系统的透明性和可维护性。同时,针对微服务架构中的跨服务链路追踪等问题,Spring Cloud还提供了相应的解决方案。

为什么重写 equals() 就一定要重写 hashCode() 方法

两个对象相等,其哈希值也相等

如果只重写equals方法,不重写hashcode方法,可能导致 a.equals(b) 表达式成立,但是hashcode不同,违反了下图描述

关于本人java面试实际碰到的问题记录 第一章初稿_第1张图片

当n等于2的次幂时,"hash%n"和"hash&(n-1)"等价

HashMap -> key 的[hashcode]对桶的长度 取模 得到数组索引下标

当落到同一个索引下标,也就是hash冲突的时候,通过拉链法来解决,形成单链表,再比较键的[equals]是否相等

Java创建对象的方式

new 反射 clone方法 序列化

序列化和反序列化

序列化:将内存中的对象,以流的方式写到磁盘中,一个个的对象片段

反序列化:将磁盘中的一个个对象片段,以流的方法读取到内存中,组成一个对象

Object类有哪些常用方法

  1. **clone()**:实现对象的浅拷贝。
  2. **getClass()**:获得运行时类型。
  3. **finalize()**:释放资源。
  4. **toString()**:一般子类都有覆盖。
  5. **equals()**:一般子类都有覆盖。在Object中,equals和==是一样的。
  6. **hashCode()**:用于哈希查找,可以减少在查找中使用equals的次数。
  7. **wait()**:使当前线程等待该对象的锁。
  8. **notify()**:唤醒在该对象上等待的某个线程。
  9. **notifyAll()**:唤醒在该对象上等待的所有线程。

重写和重载

重写(Override): 发生在父子类之间,方法名、参数列表、返回类型必须与父类相同

重载(Overload): 同名方法,参数列表不同

==和equals

==

比较两个对象时,永远比较的是对象的地址是否相等

比较基本数据类型时,比较的是值是否相等

equals

如果两个对象重写了toString方法,比较值,否则和 == 一样,比较地址

String、Integer等这些引用类型的数据,内部java已经帮我们重写了toString方法

“==”比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
equals用来比较的是两个对象的内容是否相等
由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。

int和Integer的区别

int是基本数据类型,Integer是包装类型

int默认值是0 integer默认值是null 假设某个人缺考考试,成绩为null更为合理

Integer符合面向对象编程,可以调用方法,比如 Integer.MAX_VALUE

int比较使用== integer比较使用equals integer在(-128,127)之间可以使用== 直接从缓存中获取

深拷贝和浅拷贝有什么区别        --- 这个问的也多

首先,拷贝根据基本数据类型和引用数据类型来进行一个区分

基本数据类型:值直接拷贝给新的变量

引用数据类型:

1、浅拷贝:(注意 clone() 方法在Object类中是实现浅拷贝的。) 

Person p1 = new Person(23,"zhangsan");     Person p2 = p1;

p1,p2指向同一个对象 新旧对象还是共享同一块内存

2、深拷贝:类如果实现了Cloneable接口,重写Object类中的 clone方法此时是深拷贝

Person p2 = (Person)p1.clone();

p1,p2指向不同对象

JDK和JRE的区别

JDK = JRE + 编译、运行等开发工具

JRE = JVM + JAVA系统类库(extenal libraries idea项目左下角外部jar包)

maven的package、install、deploy命令的区别

package :命令完成了项目编译、单元测试、打包功能,
        jar包(war包或其它形式的包)[没有布署到本地maven仓库和远程maven私服仓库]
install :命令完成了项目编译、单元测试、打包功能
        jar包(war包或其它形式的包)[布署到本地maven仓库,但没有布署到远程maven私服仓库]
deploy :命令完成了项目编译、单元测试、打包功能
        jar包(war包或其它形式的包)[布署到本地maven仓库和远程maven私服仓库]

双亲委派机制

当父类加载器在接收到类加载请求后,类加载器发现自己也无法加载这个类(这个情况通常是因为这个类的Class文件在父类的加载路径中不存在)这时父类会把这个信息反馈给子类,并向下委派子类加载器来加载这个类,直到这个请求被成功加载,但是一直到自定义加载器都没有找到,JVM就会抛出ClassNotFund异常。

首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。

那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass中实现你自己的加载逻辑即可

哪些数据适合放入缓存

1、即时性、数据一致性要求不高的数据。

2、访问量大且更新频率不高的数据。

类加载

加载:查找并加载类的二进制数据。将二进制数据放到运行时数据区的方法区内,通过创建java.lang.Class对象来封装类在方法区中的数据结构。

连接:

    1-验证:确保被加载类的正确性。
​
    2-准备:为类的静态变量分配内存,并将其初始化为默认值。
​
    3-解析:把类中的符号引用转换为直接引用。

初始化:为类的静态变量赋正确的初始值。

类的实例化:

1-为新对象分配内存

2-为实例变量赋默认值

3-为实例变量赋正确的初始值

4-java编译器为每一个类都至少生成一个实例初始化方法(构造方法) ,对应class文件中的

#{}和${}的区别是什么?

#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
​
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude{ DataSourceAutoConfiguration.class })。 
​
@ComponentScan:Spring组件扫描。

线程---重点

JUC包下的锁是本地锁,无法应付分布式开发。分布式锁,使用redis。

线程两次调用start会有问题吗?

IllegalThreadStateException 非法线程状态

第一次调用start,线程可能处于新建、就绪、运行状态,第二次调用就要去打破他正在执行的状态,从线程安全角度考量不太好。

线程有哪些基本状态(以及生命周期)? 

1、初始状态(New)。
线程对象被创建,即为初始状态。只在堆中开辟内存,与常规对象无异。
2、就绪状态(Ready)。
调用start()方法后,线程就进去了就绪状态。等待操作系统选择,并分配时间片。
3、运行状态(Running)。
获得时间片之后,进入运行状态,如果时间片到期,则返回就绪状态,继续等待。
4、终止状态(Terminated)。
主线程main()或者独立线程run()结束,进入终止状态,并释放持有的时间片。
5、线程等待(Waiting)。
TIMED_WAITING(时间等待):使用sleep方法时,触发的等待是有时间限制的,称为期限等待。
WAITING:使用join方法时,需要等当前线程执行完毕或者中断,这个时间是无法确定的,称为无限期等待。
6、线程阻塞(Blocked)。
处理运行状态的线程,因为某种原因放弃了CPU的使用权,而停止运行。此时线程进入阻塞状态,知道其进入就绪状态,并重新获取到CPU的使用权才会重新进入运行状态。
​

Java中实现多线程有几种方法?

继承Thread类;
实现Runnable接口;
实现Callable接口通过FutureTask包装器来创建Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)

volatile和synchronized有什么区别

volatile 本质是告诉JVM当前工作内存中的值是不确定的,需要从主内存中获取
synchronized 锁住资源,只有抢到的锁的线程才可以执行,其他线程阻塞
​
volatile仅能保证变量修改的可见性,不能保证原子性   
synchronized既能变量修改的可见性,也能保证原子性
​
volatile仅能使用在变量上   synchronized可以使用在变量、方法和类上

synchronized和lock的区别

synchronized是java内置的关键字 Lock是java类

锁的状态:synchronized 无法判断 lock.tryLock(); 可以判断

锁的释放:synchronized 自动释放锁 lock.unLock(); 手动释放,加了几次,就得释放几次

性能:synchronized 是重量级锁,适合于少量的同步代码块

sleep和wait有什么区别

sleep是Thread类中的方法 wait是Object类中的方法

sleep:让出CPU线程,但是监控状态保持,所以线程不会释放对象锁

wait:线程释放对象锁,进入等待 notify进行唤醒

线程有哪些方法

start:开启一个新的线程

run:JVM进行调度,不会启动新的线程

join:将其他线程合并到当前线程,当前线程阻塞,直到其他线程执行结束

yield:暂停当前线程,重新抢夺CPU资源

sleep:睡眠,不会释放锁

interrupt :中断线程的睡眠,依靠java的异常处理机制

currentThread().getId

currentThread().getName

守护线程

setDaemon 设置守护线程 (发音:低们 )   小弟们跟着大哥走,大哥死了,小弟也得死

public class ThreadTest14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Secondary();
        t.setName("备份数据的线程");
        // 启动线程之前将Secondary线程设置为守护线程
        t.setDaemon(true);
        t.start();
        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
        Thread.sleep(1000);
    }
}
​
class Secondary extends Thread{
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Executor线程中内部出现数据不一致怎么办

可以使用线程安全的集合和原子变量

池化技术的作用

1、不需要每次创建和销毁,节约资源,响应更快

2、限制个数,避免系统运行缓慢/崩溃

3、资源复用

线程池(Java中有哪些方法获取多线程)  --- 重点

前言

获取多线程的方法,我们都知道有三种,还有一种是实现Callable接口

  • 实现Runnable接口

  • 实现Callable接口

  • 实例化Thread类

  • 使用线程池获取

Callable接口

callable接口,重写实现其方法的时候,有返回值,而且可以感知异常

class MyRunnable implements Runnable{
​
    @Override
    public void run() {
​
    }
}
​
class MyCallable implements Callable{
​
    @Override
    public Object call() throws Exception {
        return 1024;
    }
}

思考: Thread类的构造方法没有Callable接口,那我怎么使用Callable呢?
​
Thread类的构造方法有Runnable接口,可以让 Runnable接口 和 Callable接口 产生联系!
既然需要 Runnable接口,那就准备一个 Runnable接口 的实现类 FutureTask
然后在 FutureTask 类传入 Callable接口 就能直接使用
但是为了后期更好的拓展 FutureTask
可以将 FutureTask 上层再写一个接口    RunnableFuture接口
让 RunnableFuture接口 既实现 Runnable接口 , 又实现 Future接口
这样满足要求的情况下,FutureTask其父接口也可以进行拓展!

关于本人java面试实际碰到的问题记录 第一章初稿_第2张图片

关于本人java面试实际碰到的问题记录 第一章初稿_第3张图片

Futuretask 未来任务,可以先让之后要做的某个任务先抽出来,得到返回值后,再和其他任务汇合!

关于本人java面试实际碰到的问题记录 第一章初稿_第4张图片

class MyCallable implements Callable{
​
    @Override
    public Integer call() throws Exception {
        System.out.println("call() 方法被调用");
        return 1024;
    }
}
​
​
​
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask,"AA").start();
        int result = 100;
        System.out.println(futureTask.get() + result);
    }
}

最后需要注意的是 要求获得Callable线程的计算结果,如果没有计算完成就要去强求结果,会导致阻塞,直到计算完成

所以一般将 futureTask.get(); 放置在最后,否则会阻塞其他线程

线程池的好处

多核处理的好处是:省略的上下文的切换开销

原来我们实例化对象的时候,是使用 new关键字进行创建,到了Spring后,我们学了IOC依赖注入,发现Spring帮我们将对象已经加载到了Spring容器中,只需要通过@Autowrite注解,就能够自动注入,从而资源复用!

现在需要线程也是类似,new Thread();

因此使用多线程有下列的好处

  • 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。

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

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

架构说明

Java中线程池是通过Executor框架实现的,该框架中用到了Executor,Executors(代表工具类),ExecutorService,ThreadPoolExecutor这几个类。

关于本人java面试实际碰到的问题记录 第一章初稿_第5张图片

工作原理

关于本人java面试实际碰到的问题记录 第一章初稿_第6张图片

文字说明

  1. 在创建了线程池后,等待提交过来的任务请求

  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断

    1. 如果正在运行的线程池数量小于corePoolSize,那么马上创建线程运行这个任务

    2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

    3. 如果这时候队列满了,并且正在运行的线程数量还小于maximumPoolSize,那么还是创建非核心线程like运行这个任务;

    4. 如果队列满了并且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行

  4. 当一个线程无事可做操作一定的时间(keepAliveTime)时,线程池会判断:

    1. 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉

    2. 所以线程池的所有任务完成后,它会最终收缩到corePoolSize的大小。

常用三种创建线程池方式

newFixedThreadPool          
newSingleThreadExecutor 
上面这两种使用 LinkedBlockingQueue 队列最大长度为 Integer.MAX_VALUE  候客区21亿把椅子?
newCacheThreadPool
这种 maximumPoolSize 最大为 Integer.MAX_VALUE   办理窗口21亿个窗口?
底层 都是  ThreadPoolExecutor
​
注意:工作中,这三种都不用!生产中,只用自定义的!

关于本人java面试实际碰到的问题记录 第一章初稿_第7张图片

关于本人java面试实际碰到的问题记录 第一章初稿_第8张图片

线程池执行方法 submit,带返回值task execute 不带返回值

关于本人java面试实际碰到的问题记录 第一章初稿_第9张图片

  • Executors.newFixedThreadPool(int i) :创建一个拥有 i 个线程的线程池 【一池固定线程】

    • 执行长期的任务,性能好很多

    • 创建一个定长线程池,可控制线程数最大并发数,超出的线程会在队列中等待

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 银行有5个窗口
        ExecutorService executor = Executors.newFixedThreadPool(5);
        // 模拟10个业务
        try {
            for (int i = 1; i <= 10; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "\t办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            executor.shutdown();
        }
    }
}

关于本人java面试实际碰到的问题记录 第一章初稿_第10张图片

  • Executors.newSingleThreadExecutor:创建一个只有1个线程的 单线程池 【一池单线程】

    • 一个任务一个任务执行的场景

    • 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 银行有1个窗口
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 模拟10个业务
        try {
            for (int i = 1; i <= 10; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "\t办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            executor.shutdown();
        }
    }
}

关于本人java面试实际碰到的问题记录 第一章初稿_第11张图片

  • Executors.newCacheThreadPool(); 创建一个可扩容的线程池 【一池多线程】

    • 执行很多短期异步的小程序或者负载教轻的服务器

    • 创建一个可缓存线程池,如果线程长度超过处理需要,可灵活回收空闲线程,如无可回收,则新建新线程

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 银行有N个窗口
        ExecutorService executor = Executors.newCachedThreadPool();
        // 模拟10个业务
        try {
            for (int i = 1; i <= 10; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "\t办理业务");
                });
                // 睡眠一秒,那只有一个线程执行
//                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            executor.shutdown();
        }
    }
}

关于本人java面试实际碰到的问题记录 第一章初稿_第12张图片

  • Executors.newScheduledThreadPool(int corePoolSize):线程池支持定时以及周期性执行任务,创建一个corePoolSize为传入参数,最大线程数为整形的最大数的线程池

七大参数

线程池在创建的时候,一共有7大参数

corePoolSize:     核心线程数,线程池中的常驻核心线程数   默认长期工作的线程
在创建线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
当线程池中的线程数目达到corePoolSize后,就会把到达的队列放到缓存队列中
​
maximumPoolSize:  线程池能够容纳同时执行的最大线程数,此值必须大于等于1、
相当有扩容后的线程数,这个线程池能容纳的最多线程数,队列满了,最大线程数 - 核心线程数 去处理任务
​
keepAliveTime:  多余的空闲线程存活时间
当线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余的空闲线程会被销毁
直到 只剩下corePoolSize个线程为止
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
​
unit:  keepAliveTime 的单位
​
workQueue:  任务队列,被提交的但未被执行的任务(类似于银行里面的候客区)
LinkedBlockingQueue:  链表阻塞队列
SynchronousBlockingQueue:  同步阻塞队列
​
threadFactory:  表示生成线程池中工作线程的线程工厂,用于创建线程池 一般用默认即可
​
handler:  拒绝策略
当队列满了并且工作线程大于线程池的最大线程数时,如何来拒绝请求执行的Runnable的策略
//   七大参数
corePoolSize:[5]   相当于new了5个Thread,但是没有接收任务来start
核心线程数[一直存在,除非设置了allowCoreThreadTimeOut];
线程池创建好以后就准备就绪的线程数量,就等待来接收异步任务
    
maximumPoolSize:最大线程数量;控制资源,避免一直 new start
    
keepAliveTime:存活时间。
当前的线程数量大于核心线程数的前提下,只要空闲的线程大于指定的keepAliveTime,则释放空闲线程。
空闲线程:(maximumPoolSize - corePoolSize)     类似于:空闲的时候解雇临时工
    
TimeUnit unit:时间单位(比如:存活多久?)
    
BlockingQueue workQueue:阻塞队列。
如果任务有很多,就会将目前多的任务放在队列里面。
只要有线程空闲,就会去队列里面取出新的任务继续执行。
    
ThreadFactory threadFactory:线程的创建工厂
    
RejectedExecutionHandler handler:如果队列满了,按照我们指定的拒绝策略拒绝执行任务
    
    
面试题:
    现在有一个线程池,核心线程数是7个,最大线程数是20个,队列最大为50。若有100个并发请求进来怎么分配?
    
7个线程会立即得到执行,50个会进入队列,队列满了,再开启13个新线程执行。剩下的30个使用拒绝策略。

关于本人java面试实际碰到的问题记录 第一章初稿_第13张图片

拒绝策略

人员变多,超过MAX后,执行拒绝策略

人员变少,值班窗口比人员还多的时候,销毁多余的空闲线程

关于本人java面试实际碰到的问题记录 第一章初稿_第14张图片

以下所有拒绝策略都实现了 RejectedExecutionHandler 接口
AbortPolicy:         默认,直接抛出 RejectedExcutionException(拒绝执行)异常,阻止系统正常运行
DiscardPolicy:       直接丢弃后来的任务,不予任何处理也不抛出异常,如果允许任务丢失,这是一种好方案
CallerRunsPolicy:    该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
DiscardOldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

自定义线程池

避免 newFixedThreadPool newSingleThreadExecutor 使用 LinkedBlockingQueue 队列长度太长,例如下面给了 3

避免 newCacheThreadPool 的最大线程数太大,例如下面给了 5

关于本人java面试实际碰到的问题记录 第一章初稿_第15张图片

关于本人java面试实际碰到的问题记录 第一章初稿_第16张图片

// 9超过了最大线程数 5 + 队列数 3  AbortPolicy 会直接抛异常
new ThreadPoolExecutor.AbortPolicy()
    
for (int i = 1; i <= 9; i++) 

关于本人java面试实际碰到的问题记录 第一章初稿_第17张图片

// CallerRunsPolicy 不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
// 例如下面,main线程来调用的 executor,那么对应回退了两个个main线程
new ThreadPoolExecutor.CallerRunsPolicy()
    
for (int i = 1; i <= 10; i++) 

关于本人java面试实际碰到的问题记录 第一章初稿_第18张图片

// DiscardOldestPolicy   抛弃队列中等待最久的任务
new ThreadPoolExecutor.DiscardOldestPolicy()
    
for (int i = 1; i <= 10; i++) 

关于本人java面试实际碰到的问题记录 第一章初稿_第19张图片

线程池的合理参数设置

生产环境中如何配置 corePoolSize 和 maximumPoolSize ?

// java中查看CPU核数  打印12 说明是六核十二线程
System.out.println(Runtime.getRuntime().availableProcessors());

image-20220806220946020

@Bean作用在方法上,方法名作为 bean对象的id,返回类型做为bean对象的类型 xml中:

关于本人java面试实际碰到的问题记录 第一章初稿_第20张图片

你可能感兴趣的:(java,面试,开发语言,职场和发展)