2023 面试集锦

面试大纲

file

Java面试指南+学习指导

还有设计模式,面试的时候也会考的。

一、Java基础

①、常用的集合类型都有哪些?哪些是线程安全的?
arrayList(有顺序),HashList(无顺序), linkedList, HashMap(键不能重复), HashSet(值不能重复)
HashTable(不可以有空的Null,线程安全)

扩展:
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue

file

更多请看:
Java集合说明

②、创建线程有几种方式?
三种,Thread类,Runnable接口,匿名类

二、框架原理

Spring,springMVC,Springboot,Mybatis,springCloud框架

什么是JMM?

面试的时候被问到了,不知道怎么回答,特地找资料查看了下。

MM即为JAVA 内存模型(java memory model)。不存在的东西,是概念,是约定。因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。即达到Java程序能够“一次编写,到处运行”。

Java内存模型—JMM详解

类对象(反射机制)

类对象概念: 所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法。
类对象,就是用于描述这种类,都有什么属性,什么方法的

获取类对象有3种方式

  1. Class.forName
  2. Hero.class
  3. new Hero().getClass()

在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。

注: 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。因为还没有引入ClassLoader概念, 所以暂时不展开了。

更多详细,请查看该文章:https://how2j.cn/k/reflection/reflection-class/108.html

与传统的通过new 来获取对象的方式不同
反射机制,会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”
再通过构造器对象创建一个对象

使用反射方式,首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。 里面存放的是类的名称,和要调用的方法名。
在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。

总的来说就是框架中一个重要的点:约定优于配置,配置优于实现

反射最核心的:可以在不修改代码的情况下,利用外部文件进行对象和方法的更改

Spring

Spring是一个基于IOC和AOP的结构J2EE系统的框架。
IOC 反转控制 是Spring的基础,Inversion Of Control简单说就是创建对象由以前的程序员自己new 构造方法来调用,变成了交由Spring创建对象

DI 依赖注入 Dependency Inject. 简单地说就是拿到的对象的属性,已经被注入好相关值了,直接使用即可

IOC方式

对象的生命周期由Spring来管理,直接从Spring那里去获取一个对象。 IOC是反转控制 (Inversion Of Control)的缩写,就像控制权从本来在自己手里,交给了Spring。

打个比喻:
传统方式:相当于你自己去菜市场new 了一只鸡,不过是生鸡,要自己拔毛,去内脏,再上花椒,酱油,烤制,经过各种工序之后,才可以食用。
用 IOC:相当于去馆子(Spring)点了一只鸡,交到你手上的时候,已经五味俱全,你就只管吃就行了。

file

更多详细请看下边文章:
Java 框架之 Spring

SpringMVC

Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。SpringMVC是一种web层的mvc框架,用于替代servlet(处理响应请求,获取表单参数,表单验证等)

springmvc工作流程:
1、用户发送请求至前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView。
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、ViewReslover解析后返回具体View.
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。 
11、DispatcherServlet响应用户。

springboot自动装配

参考文章:
Springboot自动装配

bean参数获取

到此我们已经知道了bean的配置过程,但是还没有看到springboot是如何读取yml或者properites配置文件的的属性来创建数据源的?
在DataSourceAutoConfiguration类里面,我们注意到使用了EnableConfigurationProperties这个注解。

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
...
}

@EnableConfigurationProperties@ConfigurationProperties 这两个注解有什么用呢?我们先看一个例子:

@Component
@ConfigurationProperties(prefix="spring.datasource")
public class PropertiesBean {
    private String url;
    private String username;
    private String password;
    //省略getter、setter...
    @Override
    public String toString() {
        return "PropertiesBean{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

从运行结果可以看出 @ConfigurationProperties@EnableConfigurationPropertie 的作用就是:

  • @ConfigurationProperties 注解的作用是把yml或者properties配置文件转化为bean
  • @EnableConfigurationProperties 注解的作用是使@ConfigurationProperties注解生效。如果只配置 @ConfigurationProperties注解,在spring容器中是获取不到yml或者properties配置文件转化的bean的

    通过这种方式,把yml或者properties配置参数转化为bean,这些bean又是如何被发现与加载的?

    bean发现

    springboot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包的中的类,那么依赖包中的bean是如何被发现和加载的?

    我们通常在启动类中加@SpringBootApplication这个注解,点进去看

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
    ), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    public @interface SpringBootApplication {
    ...
    }

    实际上重要的只有三个Annotation:

  • @Configuration(@SpringBootConfiguration里面还是应用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan

@Configuration的作用上面我们已经知道了,被注解的类将成为一个bean配置类。
@ComponentScan的作用就是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些bean定义加载到spring容器中。
@EnableAutoConfiguration 这个注解的功能很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义。

bean加载

如果要让一个普通类交给Spring容器管理,通常有以下方法:

1、使用 @Configuration与@Bean 注解
2、使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描
3、使用@Import 方法

springboot中使用了@Import 方法

@EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector实现了DeferredImportSelector接口,

DeferredImportSelector接口继承了ImportSelector接口,ImportSelector接口只有一个selectImports方法。

总结

我们可以将自动配置的关键几步以及相应的注解总结如下:

1、@Configuration&与@Bean->基于java代码的bean配置
2、@Conditional->设置自动配置条件依赖
3、@EnableConfigurationProperties与@ConfigurationProperties->读取配置文件转换为bean。
4、@EnableAutoConfiguration、@AutoConfigurationPackage 与@Import->实现bean发现与加载。

springboot Starter

Spring Boot 对比 Spring MVC 最大的优点就是使用简单,约定大于配置。能自动把配置文件搞好,不用我们手动配置,所以说,Spring Boot 是简化配置

参考文章:
你一直用的 Spring Boot Starter 是怎么回事?

spring/springboot相关文章

Springboot自动装配到底是怎么回事
Spring @Configuration 注解及配置方法
Java 框架之 Spring

属性配置类

 /**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 *
 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
 *
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

说明:@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;

JVM原理及性能调优

所有的对象实例以及数组都要在堆上分配,堆是垃圾收集器管理的主要区域,也被称为“GC对”;也是我们优化最多考虑的地方。

可通过参数 -Xmx -Xms 来指定运行时堆内存的大小,堆内存空间不足也会抛OutOfMemoryError异常。

从本次分析中,我们可以得出如下的经验: 
1)Java应用的jvm参数Xms与Xmx保持一致,避免因所使用的Java堆内存不够导致频繁full gc以及full gc中因动态调节Java堆大小而耗费延长其周期。 

JVM内存模型:
java源代码文件会被编译成.class文件,class文件会被jvm的类装载器装载到jvm,所有的数据都在运行时数据区,优化大部分都在运行时数据区

所有的对象实例以及数组都要在堆上分配,堆是垃圾收集器管理的主要区域,也被称为“GC对”;也是我们优化最多考虑的地方。

可通过参数 -Xmx -Xms 来指定运行时堆内存的大小,堆内存空间不足也会抛OutOfMemoryError异常。

从本次分析中,我们可以得出如下的经验: 
1)Java应用的jvm参数Xms与Xmx保持一致,避免因所使用的Java堆内存不够导致频繁full gc以及full gc中因动态调节Java堆大小而耗费延长其周期。 

JVM内存模型:
java源代码文件会被编译成.class文件,class文件会被jvm的类装载器装载到jvm,所有的数据都在运行时数据区,优化大部分都在运行时数据区

file

新创建来的对象,如果在Edan可以放的下就放,如果放不下,则GC一次,看能不能放的下,如果放的下就放,如果是一个大对象,在伊甸园区(新生代)放不下,则会放入到老年代(在新生代没法处理的情况下才进入到老年代),如果老年代能够放的下则会分配内存,如果老年代还放不下(不管进入到哪个内存区域里,都要判断是否放的下),则会进行一次 FULL GC(全面GC),即大屠杀,会将新生代和老年代存放的数据进行判断,如果没用则会踢出去,再来看是否还放的下,如果老年代还是放不下,则会报内存溢出异常(out of memory)。所以,full GC 也能清理一些空间。

file

大家注意,FULL GC 非常慢,如果 MinorGC 100次才花费1秒时间,FULL GC 不到10次就得要花费1秒钟,所以,这是一个性能慢10倍的GC,后来优化监控的时候,一定要避免我们的应用经常性发生FULL GC的问题。

每次GC表示增长一岁,如果有些对象存活超过阀值,则会搬到老年代,老年代存放的是生命力持久的和大对象。

一次小的 MinorGC 会将我们的伊甸园区清理干净,如果能放到幸存者区则放到幸存者区,如果不能则放到放到老年代。

YGC(Young GC) FGC(Full GC )

FGC 要比YGC慢的多。

中间件

消息队列

消息丢失、积压、重复
防止消息丢失记住这两条:
1、做好消息确认机制(publisher,consumer【手动ack】】)
2、每一个发送的消息都在数据库做好记录。定期将失败的消息再发送一遍

消息重复:
消息消费失败,由于重试机制,自动又将消息发送出去。
消息消费成功,事务已经提交,ack时,机器宕机,导致没有ack成功

解决方案#

  • 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标识。
  • 使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识,处理过就不用再处理。
  • rabbitMQ的每一个消息都有 redilivered字段,可以获取是否被重新投递过来的,而不是第一次被投递过来的。

DDD与设计模式

DDD

领域驱动设计(DDD)是一种软件开发方法论,它强调将软件系统的设计和实现围绕着业务领域展开。其主要作用是帮助开发人员更好地理解业务领域,从而提高软件系统的质量和可维护性。

在实际应用中,DDD 可以帮助开发人员更好地理解需求,减少需求变更的风险,提高代码质量和可维护性,同时也可以加快开发进度和降低开发成本。由于其能够使得软件系统更好地反映业务需求,因此在对业务逻辑较为复杂的应用中尤其适用,例如金融、电商、医疗等行业的系统开发。

设计模式

6大原则+23种设计模式

  • 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  • 结构型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式
  • 行为型模式:共11种:策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式

2、设计模式的六大原则:

(1)开闭原则 (Open Close Principle) :

开闭原则指的是对扩展开放,对修改关闭。在对程序进行扩展的时候,不能去修改原有的代码,想要达到这样的效果,我们就需要使用接口或者抽象类

(2)依赖倒转原则 (Dependence Inversion Principle):

依赖倒置原则是开闭原则的基础,指的是针对接口编程,依赖于抽象而不依赖于具体

(3)里氏替换原则 (Liskov Substitution Principle) :

里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基础类上增加新的行为。所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现。

里氏替换原则是对 “开闭原则” 的补充,实现 “开闭原则” 的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。

(4)接口隔离原则 (Interface Segregation Principle):

使用多个隔离的接口,比使用单个接口要好,降低接口之间的耦合度与依赖,方便升级和维护方便

(5)迪米特原则 (Demeter Principle):

迪米特原则,也叫最少知道原则,指的是一个类应当尽量减少与其他实体进行相互作用,使得系统功能模块相对独立,降低耦合关系。该原则的初衷是降低类的耦合,虽然可以避免与非直接的类通信,但是要通信,就必然会通过一个“中介”来发生关系,过分的使用迪米特原则,会产生大量的中介和传递类,导致系统复杂度变大,所以采用迪米特法则时要反复权衡,既要做到结构清晰,又要高内聚低耦合。

(6)合成复用原则 (Composite Reuse Principle):

尽量使用组合/聚合的方式,而不是使用继承。

zhihu|Java常见的设计模式总结
JAVA设计模式总结之23种设计模式

Java 23种设计模式

工厂方法模式

(1) 简单工厂模式

建立一个工厂类,并定义一个接口对实现了同一接口的产品类进行创建。首先看下关系图:

(2) 工厂方法模式

工厂方法模式是对简单工厂模式的改进,简单工厂的缺陷在于不符合“开闭原则”,每次添加新产品类就需要修改工厂类,不利于系统的扩展维护。而工厂方法将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。UML关系图如下:
file

(3) 静态工厂方法模式

静态工厂模式是将工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

抽象工厂模式

高并发

高并发

高并发:缓存+异步+队排好

分布式事务

分布式系统经常出现的异常,如机器宕机、网络异常、消息丢失、数据错误、不可靠的TCP、存储数据丢失等等。
分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式事物中会涉及到对多个数据源或业务系统的操作。
典型的分布式事务场景:跨银行转操作就涉及调用两个异地银行服务;

分布式理论

1、CAP理论

CAP理论:一个分布式系统不可能同时满足一致性,可用性和分区容错性这个三个基本需求,最多只能同时满足其中两项

一致性(Consistency):数据在多个副本之间是否能够保持一致的特性。

可用性(Avaliability):是指系统提供的服务必须一致处于可用状态,对于每一个用户的请求总是在有限的时间内返回结果,超过时间就认为系统是不可用的

分区容错性(Partition tolerance):分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障。

CAP定理的应用#
放弃P(CA):如果希望能够避免系统出现分区容错性问题,一种较为简单的做法就是将所有的数据(或者是与事物先相关的数据)都放在一个分布式节点上(属于本地都放一个系统),这样虽然无法保证100%系统不会出错,但至少不会碰到由于网络分区带来的负面影响

放弃A(CP):其做法是一旦系统遇到网络分区或其他故障时,那受到影响的服务需要等待一定的时间,应用等待期间系统无法对外提供正常的服务,即不可用

放弃C(AP):这里说的放弃一致性,并不是完全不需要数据一致性,是指放弃数据的强一致性,保留数据的最终一致性。

分布式系统中实现一致性的 raft 算法:
任何一个节点都有三种状态:随从,候选者,领导

CP面临的问题:
对于多数大型互联网应用的场景,主机众多,部署分散,而且现在集群的规模越来越大,所以,节点故障、网络故障是常态,而且要保证服务可用性达到 99.999999%(N个9),即保证P和A,舍弃C。

2、BASE理论

BASE是基本可用,软状态,最终一致性。是对CAP中一致性和可用性权限的结果,是基于CAP定理演化而来的,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特定,采用适当的方式来使系统达到最终一致性

强一致性、弱一致性、最终一致性:
对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这就是强一致性。
如果能容忍后续的部分或者全部访问不到,则是弱一致性,如果经过一段时间能访问到更新后的数据,则是最终一致性。

刚性事务:遵循 ACID 原则,强一致性。
柔性事务:遵循 BASE 理论,最终一致性。
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。

柔性事务-可靠消息+最终一致性方案(异步确保型)

实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送,只有在得到确认发送指令后,实时消息服务才会真正发送。

在我们的业务中,最终会使用4、5方案来解决分布式事务。

Seata分布式解决方案:

Seata的AT模式是二阶段提交协议(2PC),第一阶段将本地事务直接提交,第二阶段想要回滚的时候,是通过回滚日志(日志表)做的反向补偿,数据库原来是多少又改了回来。

Seata应用场景:后台管理系统,比如添加商品,优惠、库存、积分、会员要成功都成功,要失败都失败,对于并发性能不高的可以使用Seata来处理分布式事务。
如果并发性能要求很高的,比如下单,则需要使用最终一致性,RMQ发消息,保证消息的可靠性(发送端和接收端确认),不能达到强一致性,但能达到软柔性事务的最终一致性。

下单属于高并发场景,为了保证高并发,不推荐使用seata,Seata用了很多锁机制,因为是加锁,相当于把并发变为串行了,如果多个订单下来,就得进行排队,等待上一个人处理完了,释放了锁,才能继续下单,这样系统可能就没法用了,提升不了效率,可以发消息给库存服务。
可以使用延时队列来处理,延时队列做一个定时工作。

延时队列

场景:
比如未付款订单,超过一定时间后,系统自动取消订单并释放占有的库存。
常用解决方案:
spring的schedule定时任务轮训数据库
缺点:
消耗系统内存,增加了数据库的压力、存在较大的时间误差
解决:
Rabbit的消息 TTL 和私信Exchange结合。

死信:Dead Letter Exchange(DLX)

  • 上面的消息的TTL到了,消息过期了。
  • 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由。

Dead Letter Exchange 其实就是一种普通的 exchange,和创建其他exchange一样。只是在某一个设置Dead Letter Exchange 的队列中有信息过期了,会自动触发消息的转发,发送到 Dead Letter Exhange中去。
我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某一个指定的交换机,结合二者,其实就可以实现一个延时队列。

RMQ在解决分布式事务一致性问题上非常强大,不仅实现了解耦,而且还保证了可靠消息+最终一致性。

ThreadLocal

线程变量
拦截器想要共享变量,则需使用ThreadLocal。
ThreadLocal线程的核心原理,其实很简单,在一个Map里边放一个线程ID和对应线程的数据。

缓存

本地缓存可以在单体应用中使用,如果分布式的就会出现问题。

本地缓存模式下的分布式有问题,会造成缓存不统一,所以,不应该再使用本地模式的缓存
分布式缓存,分布式模式下,所有的微服务都共用同一个缓存中间件。

高并发下缓存失效的三个问题:

1、缓存穿透-查询一个不存在的数据

缓存穿透#
指查询一个一定不存在的数据,由于缓存是不命中,将去查数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
风险#
利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃。
解决#
null结果缓存,并加入短暂的过期时间。

缓存雪崩

缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一个时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决#
原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体的失效事件。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某一时间点被高并发的访问,是一种非常“热点“的数据。
如果这个key在大量请求同时进来前刚好失效,那么所有对这个key的查询都落在db,我们成为缓存击穿。

解决#
加锁来处理。
大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db。

小结:
穿透:查询一个永不存在的数据
雪崩:缓存key同一时间大面积失效
击穿:某一个单热点被高频访问,在前一点突然失效。

结果方案:
1、空结果缓存:解决缓存穿透
2、设置过期时间(加随机值),解决缓存雪崩
3、加锁:解决缓存击穿

异步与线程池

开发中为什么使用线程池?
降低资源的消耗#
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
提高响应速度#
因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行。
提高线程的可管理性#
线程池会根据当前系统特点对池内的线程进行优化处理,减少线程创建和销毁带来的系统开销。

// 或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,TimeUnit unit, workQuene,threadFactory,handler);

面试:一个线程池 core 7, max 20, queue:50, 100并发进来怎么分配?
答:先有 7 个能直接得到执行,接下来 50 个进入阻塞队列,再多开10个线程继续执行。现在 70 个被安排上了。剩下 30 个默认拒绝策略。
注意:当 core满了 不会立即创建新的线程,而是将进来的任务放入到阻塞队列中,当阻塞队列满了之后,才会直接新开线程执行,最大只能开到 Max指定的数量。

五、网络

三次握手

三次握手原理:

第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;

第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;

第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。

其中:SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段。

TCP 三次握手和四次挥手

可通过以下趣味图解理解三次握手:
file

举例
鸟哥上课谈到 TCP 最常做的事就是,叫一个同学起来,实际表演三向交握给大家看!

  1. 鸟哥说:A同学你在不在?
  2. A同学说:我在!那鸟哥你在不在?
  3. 鸟哥说:我也在

此时两个人就确认彼此都可以听到对方在讲啥,这就是可靠连线啦! ^_^

四次挥手

一文搞懂TCP的三次握手和四次挥手

TCP的三次握手和四次挥手实质就是TCP通信的连接和断开

三次握手:为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。

四次挥手:即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。

file

四次挥手原理:

第1次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;

第2次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态;

第3次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;

第4次挥手:客户端收到FIN后,客户端t进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手。

其中:FIN标志位数置1,表示断开TCP连接。

可通过以下趣味图解理解四次挥手:
file

MySQL

各种锁机制

1、数据库悲观锁#

select * from xxx where id = 1 for update;

悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,需要根据实际情况选用。另外注意的是,id字段一定是主键或唯一索引,不然可能造成锁表的结果,处理起来非常麻烦。

2、数据库乐观锁#
这种方法适合在更新的场景中:

update t_goods set count = count-1,version=version+1 where good_id=2 and version = 1

分布式事务

一、本地事务
原子性(Atomicity)、一致性(Consistency)、隔离性或独立性(isolation)、持久性(Durability),简称就是 ACID。

事务的隔离级别:
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。
这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

Mysql的默认隔离级别是Repeatable read。

5、本地事务问题#
在提交订单方法上,我们加了本地事务 @Transactional,当订单创建失败时,会自动回滚相关的数据,但是该方法包含了远程调用锁库存等服务,如何回滚远程的锁库存数据是一个问题;还有一个问题,当远程锁库存成功,但是由于网络等问题,响应超时了,这时还以为锁库存失败了,订单就会自动回滚,这就会出现一个很严重的问题,订单回滚了,而锁定的库存没有回滚。

分布式事务:最大原因,网络问题+分布式机器。

进程和线程

关于进程与线程的简单理解(以工厂举例:cup-》工厂,车间 -》进程,线程 -》工人),可以参考阮一峰的博文:进程与线程的一个简单图文解释

何为进程?

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程

何为线程?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

Java 程序天生就是多线程程序,我们可以通过 JMX 来看看一个普通的 Java 程序有哪些线程,代码如下。

public class MultiThread {
    public static void main(String[] args) {
        // 获取 Java 线程管理 MXBean
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程 ID 和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
        }
    }
}

操作系统的设计,因此可以归结为三点:

(1)以多进程形式,允许多个任务同时运行;

(2)以多线程形式,允许单个任务分成不同的部分运行;

(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

(完)

更多请参见该文章:https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html


相关文章:
分布式事务-CAP 和 BASE 理论以及几种方案
how2j|反射机制系列教材
Springboot自动装配到底是怎么回事
Spring @Configuration 注解及配置方法
Java 框架之 Spring
zhihu|Java常见的设计模式总结
JAVA设计模式总结之23种设计模式
Java面试指南+学习指导
进程与线程的一个简单图文解释

为者常成,行者常至