文章目录
-
- 1.计算机网络
-
-
- 1.介绍一下TCP/IP五层模型?
- 2.**什么是TCP三次握手、四次挥手?**
- **3.HTTPS和HTTP的区别是什么?**
- 4.**浏览器输入www.taobao.com回车之后发生了什么**?
- 5.**什么是TCP的粘包、拆包问题?**
- 6.http执行流程
- 7.解释一下DNS?
- 8.ARP和RARP的区别是什么?
- 9.交换机和路由器的区别是什么?
- 10.TCP是如何保证可靠传输的?
-
- 1.三次握手建立连接
- **2.四次挥手关闭连接**
- 3.超时重传机制
- 4.确认应答机制(ACK)
- 5.数据包序列号确保顺序传输
- 6.**校验和字段进行数据校验**
- [7. **流量控制**](#7. 流量控制)
- 11.cookie,session,token的区别是什么?
- 12.ping的原理是什么?
- 13.什么是IPV6?和IPV4有什么区别?
- 14.什么是CDN?
- 15.TCP和UDP的区别是什么?
- 16.什么是网络分区?
-
- 2.集合
- 3.泛型
- 4.反射
-
-
- 1.什么是反射机制?
- [2. 反射的性能问题是什么?如何优化?](#2. 反射的性能问题是什么?如何优化?)
- 3.反射的基本步骤是什么?
- 4.获取反射对象的方式?
-
- 5.注解
- [6.Stream API](#6.Stream API)
-
-
- [1.**什么是 Stream 的中间操作和终端操作?**](#1.什么是 Stream 的中间操作和终端操作?)
- 2.**什么是惰性求值?**
- [3.**如何使用 reduce 进行聚合操作?**](#3.如何使用 reduce 进行聚合操作?)
- [4. **Stream 可以重用吗?为什么?**](#4. Stream 可以重用吗?为什么?)
- [5. **Predicate、Function、Supplier 和 Consumer 接口的作用是什么?**](#5. Predicate、Function、Supplier 和 Consumer 接口的作用是什么?)
-
- 7.SSM框架
-
-
- 1.实现Spring核心机制
- 2.实现SpringMVC核心机制
- 3.实现MyBatis核心机制
- 4.Spring的单例bean是线程安全的吗?
- 5.什么是AOP?事务是如何实现的?
- 6.Spring事务失效的场景
- [7.Spring Bean的三级缓存是怎么解决循环依赖的?](#7.Spring Bean的三级缓存是怎么解决循环依赖的?)
- 8.**Spring解决循环依赖一定需要三级缓存吗?**
- 9.Spring框架使用的设计模式?
- 10.MyBatis延迟加载?
- 11.MyBatis一级和二级缓存
- 12.Spring和SpringMVC常见注解?
-
- 8.微服务
-
-
- 1.实现RPC核心机制
- 2.SpringBoot的自动配置原理?
- 3.SpringBoot如何排除自动配置类?
- [4.Spring Boot 的启动流程?](#4.Spring Boot 的启动流程?)
- [5.Spring Cloud 五大组件](#5.Spring Cloud 五大组件)
- 6.注册中心的作用
- 7.Nacos和Eureka的区别
- 8.Ribbon负载均衡
- 9.微服务限流
- 10.CAP定理有了解过吗?Base理论呢?
- 11.分布式服务的接口,幂等性如何设计?
- 12.分布式任务调度
-
- 9.设计模式
-
-
- 1.单例模式
- 2.模板方法模式
- 3.生成器模式
- 4.简单工厂
-
- [1.Operation.java 抽象运算符](#1.Operation.java 抽象运算符)
- 2.Add.java
- 3.Sub.java
- 4.Mul.java
- 5.Div.java
- [6.OperationFactory.java 运算符工厂](#6.OperationFactory.java 运算符工厂)
- 7.Client.java
- 8.总结
- 5.工厂方法
-
- [1.User.java 一个bean](#1.User.java 一个bean)
- [2.IUser.java 要创建的对象接口](#2.IUser.java 要创建的对象接口)
- 3.AccessUser.java
- 4.SqlServerUser.java
- [5.IFactory.java 工厂方法接口](#5.IFactory.java 工厂方法接口)
- 6.SqlServerFactory.java
- 7.SqlServerUser.java
- 8.Client.java
- 9.总结
- 6.抽象工厂
- 7.反射改进抽象工厂
- 8.策略模式
-
- [1.Strategy.java 策略接口](#1.Strategy.java 策略接口)
- 2.StrategyA.java
- 3.StrategyB.java
- 4.StrategyC.java
- 5.Context.java
- 6.Client.java
- 7.总结
- 9.适配器模式
-
- [1.Duck.java 鸭子接口一套规范,可以叫,可以飞](#1.Duck.java 鸭子接口一套规范,可以叫,可以飞)
- [2.Tuckey.java 火鸡接口一套规范,也是可以叫,可以飞,但是叫的名字不一样](#2.Tuckey.java 火鸡接口一套规范,也是可以叫,可以飞,但是叫的名字不一样)
- [3.TurkeyAdapter.java 适配火鸡接口到鸭子接口的规范](#3.TurkeyAdapter.java 适配火鸡接口到鸭子接口的规范)
- 4.DuckTestDrive.java
- 5.总结
- 10.JDK代理
-
- [**1.ITeacherDao.java** 要代理的接口](#1.ITeacherDao.java 要代理的接口)
- [2.TeacherDao.java 实现类](#2.TeacherDao.java 实现类)
- 3.ProxyFactory.java
- 4.Client.java
- 11.cglib代理
-
- [1.TeacherDao.java 被代理的类](#1.TeacherDao.java 被代理的类)
- 2.TeacherDaoProxyFactory.java
- 3.Client.java
- 4.JDK代理和cglib代理的区别
-
- 10.Redis
-
-
- [1.**Redis 支持哪几种数据类型**](#1.Redis 支持哪几种数据类型)
- 2.**Redis的持久化机制是怎样的?**
- 3.双写一致性
- 4.缓存的过期策略
- 5.数据淘汰策略
- 6.缓存穿透
- 7.缓存击穿
- 8.缓存雪崩
- 9.自己实现分布式锁
- 10.Redis集群的方案
- 11.Redis是单线程的,但是为什么快?
- 12.一致性哈希算法你有了解过吗?
-
- 11.RabbitMQ
- 12.**MySQL**
-
-
- 1.事务
-
- [1.事务的特性 ACID](#1.事务的特性 ACID)
- 2.并发事务问题
- [3.undo log 和redo log的区别?](#3.undo log 和redo log的区别?)
- 4.事务的隔离性是如何保证的呢?
- 5.解释一下mvcc
- 2.索引
- 3.锁机制
- 4.SQL优化的经验
-
- 1.SQL语句优化
- [2.union all 与union的区别](#2.union all 与union的区别)
- 5.主从复制
-
- 13.JUC
- 14.线程基础知识
1.计算机网络
1.介绍一下TCP/IP五层模型?
2.什么是TCP三次握手、四次挥手?
3.HTTPS和HTTP的区别是什么?
4.浏览器输入www.taobao.com回车之后发生了什么?
1.URL解析,对URL进行编码,之后查看浏览器是否缓存了该页面
2.DNS查询,得到ip
3.建立连接,进行TCP三次握手
4.将请求封装为http报文,经过网关和路由器发送给server
5.请求发送到Nginx服务器,负载均衡到某个服务器,交给MVC处理
6.接下来就是MVC的执行流程了
7.扩展:http报文
从外到里分别为以太网首部+ip首部+tcp首部+http首部
5.什么是TCP的粘包、拆包问题?
6.http执行流程
1.URL解析,对URL进行编码,之后查看浏览器是否缓存了该页面
2.DNS查询,得到ip
3.建立连接,进行TCP三次握手
4.将请求封装为http报文,经过网关和路由器发送给服务器
5.服务器处理请求并返回http响应
6.客户端接受并解析响应
7.关闭连接
7.解释一下DNS?
DNS最重要的目的就是将域名翻译成ip地址。
DNS解析的过程是,输入域名,按照以下顺序查询ip,浏览器缓存,操作系统缓存,OS hosts缓存,路由器缓存,ISP服务器缓存,根域名服务器缓存,顶级域名服务器缓存,权威域名服务器缓存。
8.ARP和RARP的区别是什么?
1.概念介绍
ARP和RARP都是网络通信协议,用于将ip地址和mac地址进行转换。
2.功能不同
ARP协议用于将ip地址转换为mac地址、RARP协议用于将mac地址转换为ip地址。
3.工作方式不同
ARP协议是一种广播协议,当一台主机需要知道另一台主机的mac地址时,直接在本地局域网广播一个ARP请求包,所有的主机都能够收到这个请求包,但是只有目标主机会响应这个请求,将自己的mac地址发送回来。
RARP协议则是向预定义的RARP服务器发出请求,请求服务器返回自己的ip地址。
9.交换机和路由器的区别是什么?
在OSI七层模型中,交换机主要工作在数据链路层,路由器工作在网络层。
交换机转发所依据的对象是物理地址,也就是mac地址,路由器转发所依据的对象是网络地址,也就是ip地址。
交换机主要用于组建局域网,而路由器主要功能是连接局域网。
10.TCP是如何保证可靠传输的?
1.三次握手建立连接
2.四次挥手关闭连接
3.超时重传机制
超时机制 是 TCP 的核心之一。当发送方发送数据包后,等待接收方的 ACK。如果在预设的时间内没有收到 ACK,发送方会认为数据丢失了或没有成功到达接收方,于是触发超时重传,即重新发送数据。
4.确认应答机制(ACK)
每当接收方收到一个数据包后,会向发送方发送一个 确认应答(ACK)。
5.数据包序列号确保顺序传输
TCP 通过为每个数据包分配一个 序列号,保证数据包的顺序传输。
6.校验和字段进行数据校验
每个TCP数据包中都有一个 校验和 字段,用来检查数据在传输过程中是否损坏。
7. 流量控制
TCP 使用 滑动窗口 机制来进行流量控制。滑动窗口指的是发送方允许未确认的数据的最大数量。接收方会通过通告窗口大小(window size)来告诉发送方它可以接收多少数据,避免接收方被数据包淹没。
11.cookie,session,token的区别是什么?
12.ping的原理是什么?
ping是一个网络工具,用来测试两台机器的网络连通性。原理是基于ICMP协议,通过发送ICMP回显请求报文。并等待目标设备返回ICMP回显应答报文,来判断网络是否畅通。
另外ping是不需要端口的,因为ping是一个应用层直接使用网络协议的,不涉及到传输层,所以不需要指定端口。
13.什么是IPV6?和IPV4有什么区别?
IPV6是下一代网络协议,旨在解决IPV4的地址资源耗尽问题,并提供更高效的网络通信。
1.地址长度
IPV4地址长度32位,IPV6地址长度128位,足以满足未来全球的网络需求。
2.地址空间
IPV4的地址资源有限,地址逐渐耗尽。IPV6提供了大范围的地址空间,可以为每一个设备分配唯一的ip地址。
3.网络性能
IPV6去掉了IPV4的某些功能,比如校验和,减轻了路由器处理的负担,从而提高了网络传输效率。
14.什么是CDN?
CDN 的工作是将网站的静态内容(如图片、CSS、JavaScript 文件、视频等)复制并分发到全球各地的缓存节点中(也叫做 边缘节点)。当用户请求内容时,CDN 会根据用户的地理位置,将请求引导到离用户最近的缓存节点,这样可以减少网络传输的延迟,提高网站的响应速度。
15.TCP和UDP的区别是什么?
1.连接性
TCP需要建立连接,UDP是无连接的协议。
2.可靠性
TCP提供可靠的传输。
UDP没有数据包序列号,确认应答,校验和,所以是不可靠的。
3.流量控制
TCP使用滑动窗口机制动态保证发送数据的速率。
UDP不提供流量控制机制。
4.速度和效率
UDP 没有连接的建立和数据确认机制,因此传输速度更快,延迟更低,但无法保证数据的可靠性。
16.什么是网络分区?
网络分区指的是,在一个分布式网络中,由于网络故障,导致网络被划分为两个或多个互不通信的区域的情况。
2.集合
1.ArrayList底层实现原理
2.ArrayList和LinkedList的区别是什么?
3.HashMap的实现原理
4.HashMap的put方法的具体流程
常见属性
构造方法
添加数据
5.HashMap的扩容机制
6.HashMap的寻址算法
7.HashMap在1.7的情况下多线程死循环的情况
8.jdk7的ConcurrentHashMap实现?
9.说一下java8实现的concurrentHashMap?
3.泛型
1.什么是类型擦除?
2.有了解过泛型的通配符吗?
java
package com.sunxiansheng;
import java.util.ArrayList;
import java.util.List;
/**
* Description: 泛型通配符示例
*
* @Author sun
* @Create 2024/7/22 20:07
* @Version 1.1
*/
public class Test {
static class Father {
}
static class Son extends Father {
}
public static void main(String[] args) {
List<Son> sonList = new ArrayList<>();
// 添加元素
sonList.add(new Son());
test(sonList);
test1(sonList);
test2(sonList);
test3(sonList);
}
/**
* 无界通配符,允许读取元素,但不能写入元素(除了null)。
*
* @param list
*/
public static void test(List<?> list) {
// 读取元素,只能读取为 Object 类型
for (Object o : list) {
System.out.println(o);
}
// 尝试写入元素
// list.add("world"); // 编译错误:无法确定类型,所以不能写入任何元素
}
/**
* 上界通配符,类型为 Father 或其子类。可以读取元素,但不能写入新元素(除了null)。
*
* @param list
*/
public static void test1(List<? extends Father> list) {
// 读取元素
for (Father f : list) {
System.out.println(f);
}
// 尝试写入元素
// list.add(new Son()); // 编译错误:不能确定 list 的具体类型,因此无法安全地写入
}
/**
* 下界通配符,类型为 Son 或其父类。可以写入 Son 类型或其子类的元素,但读取时只能读取为 Object。
*
* @param list
*/
public static void test2(List<? super Son> list) {
// 读取元素
for (Object o : list) {
System.out.println(o); // 只能读取为 Object 类型
}
// 写入元素
list.add(new Son()); // 可以安全地写入 Son 或其子类的元素
// list.add(new Father()); // 编译错误:无法确定 Father 是否是 Son 的超类型
}
/**
* 非泛型的 List,可以读写任意类型的元素。
*
* @param list
*/
public static void test3(List list) {
// 读取元素
for (Object o : list) {
System.out.println(o);
}
// 写入元素
list.add("world"); // 可以写入任意类型的元素
// 再次读取
for (Object o : list) {
System.out.println(o);
}
}
}
4.反射
1.什么是反射机制?
反射是一种通过 Class 对象在运行时动态获取类的详细信息并对其进行操作的机制。Class 对象是在类加载阶段,由 JVM 在将类的二进制文件加载到内存(元空间)时创建的。这个 Class 对象可以看作是类信息的一个映射,通过它,就可以在运行时获取类的构造函数、方法、字段等信息,甚至可以动态地创建对象、调用方法或修改字段的值。
2. 反射的性能问题是什么?如何优化?
反射的性能问题主要来自于:
• 动态解析类和方法信息的开销。
• 访问控制检查(尤其是私有成员)的开销。
• JVM无法对反射调用进行优化。
优化方法:
• 缓存Method、Field、Constructor对象,避免重复获取。
• 减少反射的使用,尽可能使用普通方法调用。
• 使用****MethodHandle(Java 7引入)代替反射调用,MethodHandle具有更好的性能。
3.反射的基本步骤是什么?
1.获取 Class 对象:首先,通过类的全限定名、对象的 getClass() 方法、或 ClassName.class 来获取该类的 Class 对象。
2.获取类信息:通过 Class 对象,使用 getMethods()、getFields()、getConstructors() 等方法来获取类的构造函数、方法、字段等信息。
3.反射爆破:如果需要访问私有成员(如私有字段或方法),可以使用 setAccessible(true) 来绕过 Java 的访问控制检查。
4.进行操作:在获取了所需的类信息并修改了访问权限后,通过 invoke 方法调用方法,或通过 get/set 方法读取或修改字段的值,或者通过 newInstance 创建类的实例。
4.获取反射对象的方式?
•类字面常量:ClassName.class,适用于已知类的情况。
•类名字符串:Class.forName("ClassName"),适用于动态加载类。
•对象实例:object.getClass(),适用于在运行时获取对象类型。
•类加载器:ClassLoader.loadClass("ClassName"),用于更复杂的类加载场景。
5.注解
1.四种元注解
@Retention:控制注解的生命周期。
@Target:指定注解的适用范围(即可以用在哪些元素上)。
@Inherited:允许子类继承父类的注解。
@Documented:决定注解是否包含在Javadoc中。
2.@Retention和@Target注解的作用是什么?
@Retention:定义注解的生命周期(Retention Policy)。常见的生命周期有:
RetentionPolicy.SOURCE:注解只存在于源码中,在编译时被丢弃。
RetentionPolicy.CLASS:注解在编译时保留在类文件中,但不会加载到JVM中。
RetentionPolicy.RUNTIME:注解在运行时保留,可以通过反射获取。
@Target:定义注解可以应用的Java元素类型。常见的目标包括:
ElementType.TYPE:可以用于类、接口、枚举。
ElementType.METHOD:可以用于方法。
ElementType.FIELD:可以用于字段。
6.Stream API
1.什么是 Stream 的中间操作和终端操作?
• 中间操作 :中间操作用于对流中的数据进行转换或过滤,生成一个新的 Stream。这些操作是 惰性 的,意味着它们不会立即执行,而是仅仅记录下需要进行的操作,直到终端操作触发执行。中间操作可以进行链式调用,但它们并不直接产生结果。
• 终端操作 :终端操作是 Stream 操作链的终点,它们会触发流的处理,并产生一个具体的结果或副作用。终端操作会 关闭流,因此在执行终端操作之后,Stream 不能再被操作。
2.什么是惰性求值?
惰性求值指的是中间操作不会立即执行,而是在终端操作触发时才会执行。这种设计可以优化性能,通过在最后一刻才计算结果来减少不必要的计算。
3.如何使用 reduce 进行聚合操作?
reduce 是一个终端操作,用于将流中的元素组合为一个值。它接受一个初始值和一个二元运算符(如加法、乘法)作为参数。
java
// 1. .reduce((p,x) -> r) 两个参数,返回一个值
Optional<Student> reduce = Stream.of(
// 创建几个学生对象
new Student("张三", 18),
new Student("李四", 19),
new Student("王五", 20),
new Student("赵六", 21)
).reduce((h1, h2) -> {
// 根据年龄比较,只留最大的
return h1.getAge() > h2.getAge() ? h1 : h2;
});
4. Stream 可以重用吗?为什么?
Stream 是不可重用的,一旦一个 Stream 被消费(即终端操作执行),它就会关闭,不能再次使用。如果需要重新处理数据,需要重新生成一个新的 Stream。
5. Predicate、Function、Supplier 和 Consumer 接口的作用是什么?
7.SSM框架
1.实现Spring核心机制
1.Spring生命周期
2.bean是怎么注入容器的?依赖注入是怎么实现的?单例多例是怎么实现的?
- 编写一个spring的xml配置文件,指定要扫描包的路径。
- 扫描bean:使用dom4j解析xml文件,就可以获取要扫描的路径,通过系统类加载器的getResource方法来加载资源,从而可以获取到这个File对象,找到所有的class文件并且通过拼接全类名,获取到Class对象,如果有@Component注解,就将其封装到BeanDefintion对象,这个bean对象主要有两个参数,bean的作用域和bean的Class对象。然后放到beanDefintionMap中,key是bean的名字,value是bean的定义信息。
- 初始化单例池:key是bean的名字,value是bean对象,扫描beanDefintionMap,如果是单例的,就直接反射创建对象放到单例池中,多例的先不管。
- 实现一个getBean方法:根据bean的名字从beanDefintionMap中获取指定的bean定义信息,如果是单例,就从单例池中获取对象,如果是多例的,就反射创建对象,然后调用依赖注入的方法,最后返回bean对象。
- 实现依赖注入:根据bean对象,获取这个bean的Class对象,可以反射判断字段中是否有自定义的依赖注入的注解,如果没有自定义的依赖注入的注解,就直接返回,那么getBean方法就会直接返回bean对象了。如果有的话就调用getBean方法来获取对象反射设置属性值,从而完成依赖注入,这样就会一直递归完成依赖注入。
3.Bean的后置处理器是怎么实现的?
- 我这里简化了一下,就是在扫描bean的时候,直接判断这个bean是否实现了BeanPostProcessor接口,具体是通过isAssignableFrom方法来实现的,如果实现了这个接口就直接创建对象放到beanPostProcessorList中。
- 在依赖注入之后,返回bean对象之前,就可以使用instanceof判断这个bean对象是否实现了InitializingBean接口,如果实现了这个接口,就调用里面的afterPropertiesSet方法即可完成初始化。
- 然后在初始化之前和之后都可以遍历beanPostProcessorList,分别执行before和after方法。
4.Spring的AOP是如何实现的?
- 自定义一个@Aspect、@Before、@After注解,在切面类上加@Component和@Aspect注解,这里我实现了前置和返回通知,切点表达式我直接写的全类名.方法名。
- 扫描bean的时候就可以扫描到被@Aspect注解标识的类,对于这个类,需要反射提取两个map。
- 第一个map是切面信息:key是切点表达式和,value是这个切面类的Class对象。
- 第二个map是通知信息:key是被aop的方法名,value也是一个map,key是通知类型(0是前置,1是返回),value是具体通知的Method对象。
- 在初始化bean之后,bean的后置处理器的after方法之前,可以解析切点表达式来判断current对象是否被代理了,如果被代理了,就可以遍历切面信息,得到current对象被aop的方法名列表。
- 然后就可以返回代理对象了,具体逻辑是,如果用户如果调用方法,就直接被代理,根据方法列表判断该方法是否被代理,如果被代理了,则根据方法名来获取这个方法的通知信息,就可以反射在前面和后面调用了。
- 需要注意的是,这里就已经将current对象换成代理对象了,所以在bean的后置处理器再处理的时候就已经是代理对象了。
2.实现SpringMVC核心机制
1.SpringMVC执行流程
2.完成请求分发
- 实现中央控制器:就是一个Servlet,里面有一个init方法,由于配置了所以在Tomcat启动的时候就会调用这个方法,这个方法主要是完成两个工作,一个是Spring容器初始化,另一个是初始化映射器。(这里是SpringMVC所以就不详细介绍Spring了)。
- 关于初始化映射器的步骤:扫描Spring容器,获取Class对象,反射初始化映射器列表,每个HandlerMapping对象里面有三个参数:请求路径、Controller对象、方法对象。
- 适配器完成请求分发:中央控制器根据请求来查询映射器,找到指定的HandlerMapping,将这个交给适配器来反射调用具体的方法。
3.完成参数解析
- 适配器得到HandlerMapping之后,调用参数解析器来完成参数解析。
- 参数的解析主要是通过Method对象获取到方法的形参类型数组然后返回一个对应的实参数组。
- 先从请求中获取request和response,判断方法的参数类型数组中有没有,如果有就放到实参列表中对应的位置
- 然后呢解析形参列表中是否有@RequestParam注解,如果有这个注解,就读取里面的name,然后从请求中获取到实参,也是放到与方法参数类型数组对应的位置,最后这个参数解析器可以返回一个实参数组。
- 最后在适配器有调用这个方法的时候传入这个实参数组即可,因为他的参数可以传一个可变参数。
4.完成视图解析
- 适配器将结果返回给中央控制器,然后中央控制器根据Method对象判断这个方法是否有@ResponseBody注解,如果有的话就直接返回json给浏览器。
- 如果没有,就将结果给视图解析器解析返回值,以冒号分割,根据冒号前面的redirect或者forward来决定是请求转发还是重定向到指定的视图。
3.实现MyBatis核心机制
1.MyBatis执行流程
2.实现xml文件映射
- 定义MapperBean和Function
- MapperBean包含了mapper接口的全类名和一个Function类型的list,就是映射了一个mapper.xml。
- Function就是一个bean,映射的是mapper.xml的每一个数据库操作,包含了sql类型、方法名、参数类型、返回类型、SQL语句。
- 动态代理mapper方法
- 构建一个代理工厂,获取Mapper接口的代理对象,传入SqlSession。
- 然后在调用接口方法的时候就会被代理,可以获取到方法信息,以及SqlSession。
- 通过反射,将接口名字 + Mapper拼接出具体的mapper.xml的文件名,从而使用dom4j解析对应的mapper.xml得到具体的MapperBean。
- 目前的情况是方法的参数有了,然后mapper.xml的信息也有了,然后就可以通过SqlSession来查询数据库了,然后使用反射封装结果。
4.Spring的单例bean是线程安全的吗?
5.什么是AOP?事务是如何实现的?
6.Spring事务失效的场景
7.Spring Bean的三级缓存是怎么解决循环依赖的?
8.Spring解决循环依赖一定需要三级缓存吗?
循环依赖不需要三级缓存,但是AOP机制就需要三级缓存。因为二级缓存的作用只是存放提前暴露的单例对象,然后直接被注入了,并没有什么机会去判断这个类型是否被AOP了。也就是说,如果直接使用二级缓存,就会导致循环依赖时,即使该类型是被AOP的,那么在被注入时,也是注入的普通对象,就会导致代理失效。所以就需要三级缓存来根据情况创建代理对象或者是普通对象。当然,要是就是想要在二级缓存去判断是否被AOP,然后创建对应的对象,也是可以的,但是这样就违反了单一职责原则。
9.Spring框架使用的设计模式?
10.MyBatis延迟加载?
假设一个用户表和一个订单表,其中用户表有Order类型的List,然后在调用user.getOrderList()方法时,就会被代理,调用时发现结果是空的,这时就会根据关联的sql,查询出Order类型的List,然后填充到这个user对象中,这样下次调用user.getOrderList()时,就会有值了。
11.MyBatis一级和二级缓存
12.Spring和SpringMVC常见注解?
8.微服务
1.实现RPC核心机制
1.环境搭建
- 一个common模块,里面有一个接口
- 然后一个消费者模块,和一个生产者模块,都引入common,也就是共享接口
- 还有一个rpc的一个模块,引入了Vert.x作为Web服务器
- rpc请求
- 服务名称其实就是接口名(为了从本地注册中心中找到Class对象,为了创建实例)
- 方法名称(为了找到目标方法)
- 参数类型(为了找到目标方法)
- 参数(为了调用目标方法)
- rpc响应
- 响应数据
- 响应信息
- 异常信息
- 本地注册中心:存储接口名与Class对象的映射,用于反射调用方法。
- 远程注册中心(Etcd):存储接口名与服务ip+端口的映射,用于定位具体的服务。
- 生产者和消费者模块都引入rpc模块,生产者用于服务监听和请求处理,消费者用于代理请求发送。
2.具体运行
- 生产者服务器启动的时候就将生产者的接口名和Class对象注册到本地注册中心,将生产者的接口名和服务ip和端口注册到远程注册中心
- 消费者通过代理工厂获取接口的代理对象,然后调用接口的方法
- 此时就会被rpc模块进行代理,可以获取到方法的名字和方法的参数类型以及方法的参数,还有接口名(通过方法对象获取),还可以根据接口名去远程注册中心,找到指定的服务ip和端口,接下来就可以构造一个rpc请求了
- 生产者就会通过服务器来接收到请求,通过接口名从本地注册中心获取Class对象,反射创建实例对象,再通过方法名称和参数类型,找到指定的方法对象,然后通过实例对象和参数就可以调用这个方法了
- 最后将返回值封装,响应给消费者的代理,然后这个代理再将结果返回给消费者
- 其实这里还有一个序列化和反序列化的步骤,我是使用的SPI机制结合读取配置文件和工厂的方式来动态指定具体的序列化器。SPI机制就是文件名是接口全类名,文件信息我是存的序列化器名字+实现类的全类名。这样就可以在工厂中进行静态初始化,将所有的序列化器,加载到工厂中,然后就可以使用hutool工具类读取properties文件,根据读取的序列化器名字从工厂中获取到具体的序列化器
2.SpringBoot的自动配置原理?
3.SpringBoot如何排除自动配置类?
4.Spring Boot 的启动流程?
1. 调用 SpringApplication.run() 方法启动
**2. 读取配置文件,**初始化监听器,监听启动过程中的各种事件
3. 创建 ApplicationContext,也就是容器实例
4. @ComponentScan 生效
扫描指定包下的bean,初始化单例池,完成依赖注入
5. @EnableAutoConfiguration生效
读取spring.factories中的指定的配置类,将其注入单例池,并完成里面属性的依赖注入,Spring会解析这个配置类,根据条件注解来将其他的bean注入容器。
6. 初始化阶段
进入初始化阶段,扫描单例池中的所有bean,调用单例池中bean的初始化方法,并在前后调用bean后置处理器的before和after方法,然后AOP是在初始化方法之后,after方法之前返回的代理对象。
7. 启动 Web 服务器(如果是 Web 应用)
Spring Boot 自动配置会注入并配置中央控制器,负责处理 HTTP 请求。
5.Spring Cloud 五大组件
6.注册中心的作用
7.Nacos和Eureka的区别
8.Ribbon负载均衡
9.微服务限流
10.CAP定理有了解过吗?Base理论呢?
11.分布式服务的接口,幂等性如何设计?
锁名可以是商品id+用户id这样就可以保证用户购买的这个商品在下单完成之前只能进行一次下单操作
12.分布式任务调度
9.设计模式
1.单例模式
1.饿汉式(静态常量,推荐)
java
package com.sun.type1;
/**
* Description: 单例模式饿汉式(静态变量)
* @Author sun
* @Create 2024/5/26 19:28
* @Version 1.0
*/
public class Singleton01 {
public static void main(String[] args) {
// 通过公有的静态方法,获取实例
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance); // 返回的是true
}
}
class Singleton {
// 1.构造器私有化
private Singleton() {}
// 2.本类的内部创建对象实例
private final static Singleton instance = new Singleton();
// 3.暴露一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2.饿汉式(静态代码块,推荐)
java
package com.sun.type1;
/**
* Description: 单例模式饿汉式(静态代码块)
* @Author sun
* @Create 2024/5/26 19:49
* @Version 1.0
*/
public class Singleton02Test {
public static void main(String[] args) {
// 通过公有的静态方法,获取实例
Singleton02 instance = Singleton02.getInstance();
Singleton02 instance1 = Singleton02.getInstance();
System.out.println(instance1 == instance); // 返回的是true
}
}
class Singleton02 {
// 1.构造器私有化
private Singleton02() {}
// 2.本类的内部有一个静态属性
private static Singleton02 instance;
// 3.使用静态代码块为静态属性初始化
static {
instance = new Singleton02();
}
// 4.暴露一个公有的静态方法,返回实例对象
public static Singleton02 getInstance() {
return instance;
}
}
3.懒汉式(线程不安全,实际开发不要使用)
java
package com.sun.type3;
/**
* Description: 单例模式懒汉式(线程不安全)
* @Author sun
* @Create 2024/5/26 20:02
* @Version 1.0
*/
public class Singleton03Test {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance);
}
}
class Singleton {
// 1.构造器私有化
private Singleton() {}
// 2.静态属性,存放本类对象
private static Singleton instance;
// 3.静态的公有方法,当使用到该方法时才会去创建instance
public static Singleton getInstance() {
// 当静态属性没有被赋值的时候再去创建
if (instance == null) {
instance = new Singleton();
}
// 如果这个静态属性被初始化过了,就直接返回,保证是单例的
return instance;
}
}
4.懒汉式(线程安全,同步方法,实际开发不推荐使用)
java
package com.sun.type4;
/**
* Description: 单例模式懒汉式(线程安全,同步方法)
* @Author sun
* @Create 2024/5/26 20:15
* @Version 1.0
*/
public class Singleton04Test {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance);
}
}
class Singleton {
// 1.构造器私有化
private Singleton() {}
// 2.静态属性,存放本类对象
private static Singleton instance;
// 3.静态的公有方法,当使用到该方法时才会去创建instance,使用synchronized实现线程安全
public static synchronized Singleton getInstance() {
// 当静态属性没有被赋值的时候再去创建
if (instance == null) {
instance = new Singleton();
}
// 如果这个静态属性被初始化过了,就直接返回,保证是单例的
return instance;
}
}
5.懒汉式(线程安全,同步代码块,解决不了同步问题)
6.双重检查(推荐使用)
java
package com.sun.type6;
/**
* Description: 双重检查
* @Author sun
* @Create 2024/5/26 20:15
* @Version 1.0
*/
public class Singleton06Test {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance);
}
}
class Singleton {
// 1.构造器私有化
private Singleton() {}
// 2.静态属性,存放本类对象,使用volatile防止指令重排序
private static volatile Singleton instance;
// 3.静态的公有方法,当使用到该方法时才会去创建instance
public static Singleton getInstance() {
// 先检查一次静态属性是否为空,这样可以过滤掉一部分
if (instance == null) {
// 如果为空,则进入同步代码块创建对象,由于还是会有多个线程进来,所以在内部需要二次检查
synchronized (Singleton.class) {
// 在此完成双重检查
if (instance == null) {
instance = new Singleton();
}
}
}
// 如果这个静态属性被初始化过了,就直接返回,保证是单例的
return instance;
}
}
7.静态内部类实现(推荐使用)
java
package com.sun.type7;
/**
* Description: 单例模式(静态内部类实现)
* @Author sun
* @Create 2024/5/26 20:51
* @Version 1.0
*/
public class Singleton07Test {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1 == instance);
}
}
class Singleton {
// 1.构造器私有化
private Singleton() {}
// 2.一个静态内部类,该类有一个静态属性,存放外部类的对象
private static class SingletonInstance {
public static final Singleton INSTANCE = new Singleton();
}
// 3.静态的公有方法,当要使用时调用内部类的静态属性,此时静态内部类会被装载,且只会被装载一次
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
8.枚举实现(推荐使用)
java
package com.sun.type8;
/**
* Description: 单例模式(静态内部类实现)
* @Author sun
* @Create 2024/5/26 20:51
* @Version 1.0
*/
public class Singleton08Test {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance2 == instance);
}
}
enum Singleton {
// 这个就相当于调用无参构造创建了这个类的一个对象,可以实现单例模式
INSTANCE;
}
9.总结
饿汉式两种:主要是靠类加载机制来实现的单例,要么直接创建,要么通过静态代码块来创建,其实没啥区别
懒汉式三种:一种是直接判空,线程不安全,第二种是同步方法,线程安全,效率低,第三种是判空内同步代码块,线程安全,但是解决不了同步的问题
双重检查:其实就是懒汉式的最佳实践,先判空再同步再判空,既解决了同步问题,又解决了效率问题
枚举实现:最佳实践,Effective Java作者推荐
2.模板方法模式
1.AbstractClass.java
java
package com.dahua;
/**
* Description: 抽象的模板类
*
* @Author sun
* @Create 2024/8/15 20:35
* @Version 1.0
*/
public abstract class AbstractClass {
/**
* 模板方法
*/
public void templateMethod() {
// 调用基本方法
baseMethod();
// 调用钩子方法
if (hookMethod()) {
// 调用基本方法
baseMethod();
}
// 调用抽象方法
abstractMethod();
}
/**
* 基本方法
*/
public void baseMethod() {
System.out.println("基本方法");
}
/**
* 钩子方法
*/
public boolean hookMethod() {
return true;
}
/**
* 抽象方法
*/
public abstract void abstractMethod();
}
2.ConcereteClass.java
java
package com.dahua;
/**
* Description: 具体实现类
*
* @Author sun
* @Create 2024/8/15 20:38
* @Version 1.0
*/
public class ConcereteClass extends AbstractClass{
/**
* 抽象方法的具体实现
*/
@Override
public void abstractMethod() {
System.out.println("子类实现的抽象方法");
}
/**
* 钩子方法,可以控制模板方法的执行
* @return
*/
@Override
public boolean hookMethod() {
return false;
}
}
3.Client.java
java
package com.dahua;
/**
* Description:
*
* @Author sun
* @Create 2024/8/15 20:39
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
ConcereteClass concereteClass = new ConcereteClass();
concereteClass.templateMethod();
}
}
4.总结
模板方法模式的抽象模板主要是有四个成员,普通方法,抽象方法,模板方法,钩子方法。
具体实现类的存在意义就是填充父类的抽象方法,然后通过重写钩子方法来改变模板方法的逻辑。
3.生成器模式
1.理论
传统的生成器模式其实有三个角色:产品、建造者、指挥者。
抽象建造者规定了具体的步骤,具体建造者实现具体的步骤,然后返回一个产品。
指挥者聚合抽象建造者,来真正的构建产品。
2.具体使用-静态内部类创建对象
java
public class Computer {
// 必选参数
private String CPU;
private String RAM;
// 可选参数
private int storage; // 存储容量
private String graphicsCard; // 显卡
// 私有化构造函数,使用Builder进行创建
private Computer(Builder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
this.graphicsCard = builder.graphicsCard;
}
// 静态内部类 Builder
public static class Builder {
// 必选参数
private final String CPU;
private final String RAM;
// 可选参数,使用默认值
private int storage = 256; // 默认存储容量
private String graphicsCard = "Integrated"; // 默认显卡
// 构造函数,初始化必选参数
public Builder(String CPU, String RAM) {
this.CPU = CPU;
this.RAM = RAM;
}
// 设置可选参数:存储容量
public Builder setStorage(int storage) {
this.storage = storage;
return this; // 返回Builder对象本身以便链式调用
}
// 设置可选参数:显卡
public Builder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
// 构建并返回一个Computer对象
public Computer build() {
return new Computer(this);
}
}
// 生成的Computer对象的toString方法,用于显示配置信息
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", Storage=" + storage + "GB, GraphicsCard=" + graphicsCard + "]";
}
}
3.总结
Effective Java 中说过当参数大于等于4个的时候就可以使用生成器模式了。
具体实现也是很简单:
- 一个类型假设有两个可选字段和两个必选字段
- 那么就用一个静态内部类,将这四个字段作为静态内部类的属性
- 其中的可选字段需要赋默认值
- 提供一个构造器,来设置这个静态内部类的必选字段的值
- 然后提供一系列的setter方法,来设置其他字段的值
- 最后提供一个build方法,就是构建一个外部类的对象,其中的属性就是静态内部类的属性值
4.简单工厂
1.Operation.java 抽象运算符
java
package com.dahua.simplefactory;
/**
* Description: 运算符抽象类
*
* @Author sun
* @Create 2024/8/9 20:33
* @Version 1.0
*/
public abstract class Operation {
public double getResult(double numberA, double numberB) {
return 0;
}
}
2.Add.java
java
package com.dahua.simplefactory;
/**
* Description:
*
* @Author sun
* @Create 2024/8/9 20:33
* @Version 1.0
*/
public class Add extends Operation {
@Override
public double getResult(double numberA, double numberB) {
return numberA + numberB;
}
}
3.Sub.java
java
package com.dahua.simplefactory;
/**
* Description:
*
* @Author sun
* @Create 2024/8/9 20:33
* @Version 1.0
*/
public class Sub extends Operation {
@Override
public double getResult(double numberA, double numberB) {
return numberA - numberB;
}
}
4.Mul.java
java
package com.dahua.simplefactory;
/**
* Description:
*
* @Author sun
* @Create 2024/8/9 20:33
* @Version 1.0
*/
public class Mul extends Operation {
@Override
public double getResult(double numberA, double numberB) {
return numberA * numberB;
}
}
5.Div.java
java
package com.dahua.simplefactory;
/**
* Description:
*
* @Author sun
* @Create 2024/8/9 20:33
* @Version 1.0
*/
public class Div extends Operation {
@Override
public double getResult(double numberA, double numberB) {
if (numberB == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return numberA / numberB;
}
}
6.OperationFactory.java 运算符工厂
java
package com.dahua.simplefactory;
/**
* Description: 根据运算符创建对应的运算类
*
* @Author sun
* @Create 2024/8/9 20:29
* @Version 1.0
*/
public class OperationFactory {
public static Operation createOperate(String operate) {
Operation operation = null;
switch (operate) {
case "+":
operation = new Add();
break;
case "-":
operation = new Sub();
break;
case "*":
operation = new Mul();
break;
case "/":
operation = new Div();
break;
default:
throw new IllegalArgumentException("运算符错误");
}
return operation;
}
}
7.Client.java
java
package com.dahua.simplefactory;
/**
* Description:
*
* @Author sun
* @Create 2024/8/9 20:38
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 我要加法
double result = OperationFactory.createOperate("+").getResult(1, 2);
System.out.println("result = " + result);
}
}
8.总结
简单工厂主要分为两个角色,具体工厂和要创建的对象。
就是在简单工厂里面做了一个判断,根据不同的类型,返回不同的对象。
5.工厂方法
1.User.java 一个bean
java
package com.dahua.factorymethodup;
/**
* Description: bean
*
* @Author sun
* @Create 2024/8/19 15:39
* @Version 1.0
*/
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.IUser.java 要创建的对象接口
java
package com.dahua.factorymethodup;
/**
* Description: 要创建的对象接口
*
* @Author sun
* @Create 2024/8/19 15:38
* @Version 1.0
*/
public interface IUser {
void insert(User user);
User getUser(int id);
}
3.AccessUser.java
java
package com.dahua.factorymethodup;
/**
* Description: AccessUser
*
* @Author sun
* @Create 2024/8/19 15:40
* @Version 1.0
*/
public class AccessUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 Access 中给 User 表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在 Access 中根据 id 得到 User 表一条记录");
return null;
}
}
4.SqlServerUser.java
java
package com.dahua.factorymethodup;
/**
* Description: SqlServerUser
*
* @Author sun
* @Create 2024/8/19 15:40
* @Version 1.0
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 SQL Server 中给 User 表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在 SQL Server 中根据 id 得到 User 表一条记录");
return null;
}
}
5.IFactory.java 工厂方法接口
java
package com.dahua.factorymethodup;
/**
* Description: 工厂方法接口
*
* @Author sun
* @Create 2024/8/19 15:41
* @Version 1.0
*/
public interface IFactory {
IUser createUser();
}
6.SqlServerFactory.java
java
package com.dahua.factorymethodup;
/**
* Description: SqlServerFactory
*
* @Author sun
* @Create 2024/8/19 15:42
* @Version 1.0
*/
public class SqlServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
}
7.SqlServerUser.java
java
package com.dahua.factorymethodup;
/**
* Description: SqlServerUser
*
* @Author sun
* @Create 2024/8/19 15:40
* @Version 1.0
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 SQL Server 中给 User 表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在 SQL Server 中根据 id 得到 User 表一条记录");
return null;
}
}
8.Client.java
java
package com.dahua.factorymethodup;
/**
* Description: 通过具体的工厂类来创建对象
*
* @Author sun
* @Create 2024/8/19 15:42
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
IFactory factory = new AccessFactory();
IUser user = factory.createUser();
user.insert(new User());
user.getUser(1);
}
}
9.总结
工厂方法模式有两个角色,抽象工厂和抽象对象。
其实就是一个具体工厂创建一个具体对象。
6.抽象工厂
1.目录
2.具体实现
1.实体类(非必须)
1.User.java
java
package com.dahua.absfactory;
/**
* Description: bean
*
* @Author sun
* @Create 2024/8/19 15:39
* @Version 1.0
*/
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
- Department.java
java
package com.dahua.absfactory;
/**
* Description: bean
*
* @Author sun
* @Create 2024/8/19 15:58
* @Version 1.0
*/
public class Department {
private int id;
private String name;
public Department() {
}
public Department(int id, String name) {
this.id = id;
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
2.要创建的对象
1.IUser.java 要创建的User对象接口
java
package com.dahua.absfactory;
/**
* Description: 要创建的对象接口
*
* @Author sun
* @Create 2024/8/19 15:38
* @Version 1.0
*/
public interface IUser {
void insert(User user);
User getUser(int id);
}
2.AccessUser.java
java
package com.dahua.absfactory;
/**
* Description: AccessUser
*
* @Author sun
* @Create 2024/8/19 15:40
* @Version 1.0
*/
public class AccessUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 Access 中给 User 表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在 Access 中根据 id 得到 User 表一条记录");
return null;
}
}
3.SqlServerUser.java
java
package com.dahua.absfactory;
/**
* Description: SqlServerUser
*
* @Author sun
* @Create 2024/8/19 15:40
* @Version 1.0
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 SQL Server 中给 User 表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在 SQL Server 中根据 id 得到 User 表一条记录");
return null;
}
}
4.IDepartment.java 要创建的Department对象的接口
java
package com.dahua.absfactory;
/**
* Description: 要创建的对象的接口
*
* @Author sun
* @Create 2024/8/19 15:58
* @Version 1.0
*/
public interface IDepartment {
void insert(Department department);
Department getDepartment(int id);
}
5.AccessDepartment.java
java
package com.dahua.absfactory;
/**
* Description: Access Department
*
* @Author sun
* @Create 2024/8/19 16:02
* @Version 1.0
*/
public class AccessDepartment implements IDepartment {
@Override
public void insert(Department department) {
System.out.println("在Access中给Department表增加一条记录");
}
@Override
public Department getDepartment(int id) {
System.out.println("在Access中根据ID得到Department表一条记录");
return null;
}
}
6.SqlServerDepartment.java
java
package com.dahua.absfactory;
/**
* Description: SQL Server Department
*
* @Author sun
* @Create 2024/8/19 16:02
* @Version 1.0
*/
public class SqlServerDepartment implements IDepartment {
@Override
public void insert(Department department) {
System.out.println("在SQL Server中给Department表增加一条记录");
}
@Override
public Department getDepartment(int id) {
System.out.println("在SQL Server中根据ID得到Department表一条记录");
return null;
}
}
3.工厂
1.IFactory.java 工厂接口
java
package com.dahua.absfactory;
/**
* Description: 工厂方法接口
*
* @Author sun
* @Create 2024/8/19 15:41
* @Version 1.0
*/
public interface IFactory {
IUser createUser();
IDepartment createDepartment();
}
2.AccessFactory.java
java
package com.dahua.absfactory;
/**
* Description: AccessFactory
*
* @Author sun
* @Create 2024/8/19 15:41
* @Version 1.0
*/
public class AccessFactory implements IFactory {
@Override
public IUser createUser() {
return new AccessUser();
}
@Override
public IDepartment createDepartment() {
return new AccessDepartment();
}
}
3.SqlServerFactory.java
java
package com.dahua.absfactory;
/**
* Description: SqlServerFactory
*
* @Author sun
* @Create 2024/8/19 15:42
* @Version 1.0
*/
public class SqlServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
7.反射改进抽象工厂
1.去掉IFactory、SqlserverFactory、AccessFactory
2.目录
3.DataAccess.java
java
package com.dahua.absfactoryup;
/**
* Description: 反射+简单工厂改进抽象工厂模式
*
* @Author sun
* @Create 2024/8/20 08:30
* @Version 1.0
*/
public class DataAccess {
private static final String assemblyName = "com.dahua.absfactoryup.";
// 这里可以根据配置文件来读取
private static final String db = "SqlServer";
public static IUser createUser() {
try {
return (IUser) Class.forName(assemblyName + db + "User").newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static IDepartment createDepartment() {
try {
return (IDepartment) Class.forName(assemblyName + db + "Department").newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4.Client.java
java
package com.dahua.absfactoryup;
/**
* Description: 通过具体的工厂类来创建对象
*
* @Author sun
* @Create 2024/8/19 15:42
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
IUser user = DataAccess.createUser();
user.insert(new User());
user.getUser(1);
IDepartment department = DataAccess.createDepartment();
department.insert(new Department());
department.getDepartment(1);
}
}
5.总结
标准的抽象工厂其实就是一个工厂创建多个对象,但是这样的话新增对象就需要更改抽象工厂和多份具体工厂。
但是如果使用反射改进,新增对象就只需要在一个类中新增一个方法即可,而切换工厂也可以通过配置文件读取。
8.策略模式
1.Strategy.java 策略接口
java
package com.dahua;
/**
* Description: 策略接口
*
* @Author sun
* @Create 2024/8/12 20:03
* @Version 1.0
*/
public interface Strategy {
/**
* 算法接口
*/
void algorithmInterface();
}
2.StrategyA.java
java
package com.dahua;
/**
* Description: 策略A
*
* @Author sun
* @Create 2024/8/12 20:05
* @Version 1.0
*/
public class StrategyA implements Strategy{
@Override
public void algorithmInterface() {
System.out.println("策略A");
}
}
3.StrategyB.java
java
package com.dahua;
/**
* Description: 策略B
*
* @Author sun
* @Create 2024/8/12 20:05
* @Version 1.0
*/
public class StrategyB implements Strategy{
@Override
public void algorithmInterface() {
System.out.println("策略B");
}
}
4.StrategyC.java
java
package com.dahua;
/**
* Description: 策略C
*
* @Author sun
* @Create 2024/8/12 20:05
* @Version 1.0
*/
public class StrategyC implements Strategy{
@Override
public void algorithmInterface() {
System.out.println("策略C");
}
}
5.Context.java
java
package com.dahua;
/**
* Description: 策略上下文,根据不同的策略执行不同的算法
*
* @Author sun
* @Create 2024/8/12 20:06
* @Version 1.0
*/
public class Context {
private Strategy strategy;
/**
* 与简单工厂结合,根据不同的类型创建不同的策略
* @param type
*/
public Context(int type) {
switch (type) {
case 1:
strategy = new StrategyA();
break;
case 2:
strategy = new StrategyB();
break;
case 3:
strategy = new StrategyC();
break;
default:
break;
}
}
public void contextInterface() {
strategy.algorithmInterface();
}
}
6.Client.java
java
package com.dahua;
/**
* Description: 客户端
*
* @Author sun
* @Create 2024/8/12 20:07
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 传入不同的策略,执行不同的算法8
new Context(1).contextInterface();
new Context(2).contextInterface();
new Context(3).contextInterface();
}
}
7.总结
策略模式主要是有两个成员,策略接口,和上下文。具体的实现就是,多个具体策略实现策略接口,提供不同的策略,上下文一般与工厂结合,根据类型不同,返回不同的策略。
9.适配器模式
1.Duck.java 鸭子接口一套规范,可以叫,可以飞
java
package com.headfirst.simpleadapter;
/**
* Description: 鸭子接口
* @Author sun
* @Create 2024/7/30 20:09
* @Version 1.0
*/
public interface Duck {
public void quack();
public void fly();
}
2.Tuckey.java 火鸡接口一套规范,也是可以叫,可以飞,但是叫的名字不一样
java
package com.headfirst.simpleadapter;
/**
* Description: 火鸡接口
* @Author sun
* @Create 2024/7/30 20:10
* @Version 1.0
*/
public interface Tuckey {
public void gobble();
public void fly();
}
3.TurkeyAdapter.java 适配火鸡接口到鸭子接口的规范
java
package com.headfirst.simpleadapter;
/**
* Description: 火鸡适配器(本来是鸭子,但是适配了一些火鸡的具体方法)
* @Author sun
* @Create 2024/7/30 20:11
* @Version 1.0
*/
public class TurkeyAdapter implements Duck{
// 适配器中聚合一个火鸡的引用
private Tuckey tuckey;
public TurkeyAdapter(Tuckey tuckey) {
this.tuckey = tuckey;
}
@Override
public void quack() {
// 火鸡的叫声
tuckey.gobble();
}
@Override
public void fly() {
// 火鸡的飞行
for (int i = 0; i < 5; i++) {
tuckey.fly();
}
}
}
4.DuckTestDrive.java
java
package com.headfirst.simpleadapter;
/**
* Description:
* @Author sun
* @Create 2024/7/30 20:14
* @Version 1.0
*/
public class DuckTestDrive {
public static void main(String[] args) {
// 创建一个火鸡
Tuckey tuckey = new WildTurkey();
// 创建一个火鸡适配器(实际上是鸭子,但是适配了火鸡的方法)
Duck duck = new TurkeyAdapter(tuckey);
// 测试火鸡适配器
System.out.println("The Turkey says...");
duck.quack();
duck.fly();
}
}
5.总结
适配器模式就是有两个接口,一个是我自己公司的接口,一个是第三方接口,他们做的事情类似,但是名字不同操作也有区别,此时就需要将第三方的接口适配到自己公司的接口,就比如oss服务,自己公司有一套规范,阿里云和腾讯云的接口也不相同,所以就需要调用他们的接口,适配到自己公司的接口上。
10.JDK代理
1.ITeacherDao.java 要代理的接口
java
package com.sun.dynamic;
/**
* Description: 被代理的接口
* @Author sun
* @Create 2024/6/7 19:25
* @Version 1.0
*/
public interface ITeacherDao {
void teach(String name);
}
2.TeacherDao.java 实现类
java
package com.sun.dynamic;
/**
* Description: 实现类
* @Author sun
* @Create 2024/6/7 19:06
* @Version 1.0
*/
public class TeacherDao implements ITeacherDao {
@Override
public void teach(String name) {
System.out.println(name + "老师正在授课中");
}
}
3.ProxyFactory.java
java
package com.sun.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* Description: 动态代理对象,可以对其代理的对象的任意方法添加任意操作
* @Author sun
* @Create 2024/6/7 19:27
* @Version 1.0
*/
public class ProxyFactory {
// 构造器聚合一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象,生成一个代理对象
public Object getProxyInstance() {
/**
* 参数说明:
* ClassLoader loader:指定当前目标对象使用的类加载器
* Class<?>[] interfaces:是目标对象实现的接口类型,使用泛型方法确认类型
* InvocationHandler h:是事件处理,当使用代理对象调用目标对象的方法时会触发
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
/**
* method:目标方法的Method对象,可以用来调用目标任何方法
* args:目标方法的参数,也就是动态代理对象调用目标任何方法时传入的参数
* @return 返回调用目标方法的返回值,也可以返回null
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method = " + method + ", args = " + Arrays.toString(args));
System.out.println("动态代理开始");
/*
invoke方法的第一个参数是目标对象,第二个参数的目标方法的参数
*/
Object result = method.invoke(target, args);
System.out.println("动态代理结束");
return result;
}
});
}
}
4.Client.java
java
package com.sun.dynamic;
/**
* Description:
* @Author sun
* @Create 2024/6/7 19:47
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建一个目标对象
ITeacherDao target = new TeacherDao();
// 得到代理对象
ITeacherDao proxy = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
// 使用代理对象调用方法
proxy.teach("李华");
}
}
11.cglib代理
1.TeacherDao.java 被代理的类
java
package com.sun.cglib;
/**
* Description: 被代理的类
* @Author sun
* @Create 2024/6/8 19:37
* @Version 1.0
*/
public class TeacherDao {
public void teach(String name) {
System.out.println(name + "老师授课中,使用的是cglib代理,不需要实现接口");
}
}
2.TeacherDaoProxyFactory.java
java
package com.sun.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Description:
* @Author sun
* @Create 2024/6/8 19:38
* @Version 1.0
*/
public class TeacherDaoProxyFactory implements MethodInterceptor {
// 构造器聚合一个目标对象
private TeacherDao target;
public TeacherDaoProxyFactory(TeacherDao target) {
this.target = target;
}
// 返回目标对象的代理对象
public TeacherDao getProxyInstance() {
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类(就是要代理的具体类)
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象
return (TeacherDao) enhancer.create();
}
/**
* 当使用代理对象调用目标对象的函数时,就会跳到这个函数,跟之前动态代理时类似
* @param o
* @param method 代理对象调用的目标对象的函数
* @param args 函数的参数
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public TeacherDao intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理模式开始");
// 调用目标对象的函数
System.out.println("method = " + method);
System.out.println("args = " + args);
Object returnVal = method.invoke(target, args);
System.out.println("cglib代理结束");
return (TeacherDao) returnVal;
}
}
3.Client.java
java
package com.sun.cglib;
/**
* Description:
* @Author sun
* @Create 2024/6/8 19:50
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建一个目标对象
TeacherDao teacherDao = new TeacherDao();
// 得到目标对象的代理对象
TeacherDaoProxyFactory teacherDaoProxyFactory = new TeacherDaoProxyFactory(teacherDao);
TeacherDao proxyInstance = teacherDaoProxyFactory.getProxyInstance();
// 使用代理对象来调用目标对象函数,则会被代理
proxyInstance.teach("小明");
}
}
4.JDK代理和cglib代理的区别
jdk代理是代理接口的,步骤:首先获取接口的代理对象,只要使用这个代理对象来调用接口的方法,就可以获取这个方法信息和目标对象(取决于传没传),获取接口代理对象有三个参数,接口的类加载器,接口的Class对象数组,实现了InvocationHandler接口的对象。
cglib代理是代理类的,步骤:首先获取类的代理对象,只要使用这个类的代理对象类调用类的方法,就可以获取这个方法的信息和目标对象(去局域传没传),获取类的代理对象需要类的Class对象,和实现了MethodInterceptor接口的对象。
10.Redis
1.Redis 支持哪几种数据类型
2.Redis的持久化机制是怎样的?
1.RDB
2.AOF
3.对比
3.双写一致性
1.问题
2.双写不一致的原因
2.延迟双删(有脏数据)
3.读写锁(强一致)
4.异步通知(最终一致性)
5.总结
4.缓存的过期策略
1.惰性删除
2.定期删除
5.数据淘汰策略
1.八种策略
2.使用建议
3.其他问题
6.缓存穿透
1.解决方案一:缓存空数据
2.解决方案二:布隆过滤器
7.缓存击穿
1.问题出现原因
2.分布式锁和逻辑过期
8.缓存雪崩
1.问题出现原因
2.解决方案
9.自己实现分布式锁
1.业务场景
我在自定义脚手架的时候实现了一个分布式锁,然后具体应用是在之前做过的一个商城项目,为了防止用户重复提交,我使用分布式锁的方式来实现的,用户在购买商品的时候,会对用户id和商品id进行加锁,如果加锁成功,就执行购买逻辑,如果加锁失败,直接快速失败。
2.考虑的问题
首先需要考虑的问题有两个:死锁问题和误删锁问题
3.死锁问题
如果key不设置过期时间,当一个服务占用了这个锁,在释放锁之前,宕机了,那么另一个服务就永远获取不到这个锁,就会导致这个死锁问题。所以就必须加过期时间,当一个服务宕机了,就会自动释放锁。
4.误删锁问题
误删锁问题产生的原因:两个服务同时进行加锁解锁操作,然后一个服务A先加锁,然后在解锁之前,锁自动释放了,此时,服务B就可以加锁,然后服务A就把服务B的锁给释放了。
解决方式:每次加锁解锁的value都加一个uuid,每个服务去解锁的时候,都需要检查一下uuid是否是当前服务的uuid,如果是才能够解锁。
但是,光是这样,还解决不了误删锁的问题,另一种导致误删锁的情况就是,在redis在释放锁的时候需要判断这个锁的值是否是当前服务的uuid,如果是再释放。但是,如果已经判断为true了,这个时候锁正好过期了,那么又会导致误删锁的问题。此时就需要用到Lua脚本来解决原子性问题,这样就可以完美解决误删锁问题了。
5.函数式编程简化操作
然后我还使用了函数式编程的方式来简化用户操作,就是用户只需要传入锁的key和一个无参无返回的Runnable函数式接口来决定加锁逻辑。至于过期时间,不传也有默认值。
10.Redis集群的方案
1.三种集群
2.主从复制
3.哨兵模式
4.分片集群
11.Redis是单线程的,但是为什么快?
IO多路复用
首先,Linux系统中有用户空间和内核空间,用户空间只能执行受限的命令,内核空间可以调度系统底层资源。
Linux为了提高效率,会把用户空间和内核空间都加入缓冲区,分别为用户缓冲区和内核缓冲区。
用户缓冲区想要调用内核缓冲区的资源,就需要等待内核缓冲区的资源就绪,而等待的方式一共有三种,阻塞IO、非阻塞IO、IO多路复用。
IO多路复用,就是单线程同时监听多个socket,并在某个socket可读,可写时得到通知,避免无效的等待。目前的IO多路复用采用的都是epoll模式实现,他会在通知用户进程socket就绪的时候,把就绪的socket直接写入用户空间,不需要遍历socket来判断socket是否就绪,提升了性能。
12.一致性哈希算法你有了解过吗?
11.RabbitMQ
1.RabbitMQ如何保证消息可靠性?
1.消息可能丢失的场景
2.生产者确认机制
3.消息持久化
4.消费者失败重试机制
2.延迟消息
1.死信交换机
2.延迟消息插件
3.如何防止RabbitMQ被重复消费
• 唯一 ID :在每条消息中携带一个唯一的 消息 ID 或 业务唯一标识(如订单号、事务 ID 等)。消费者在处理每条消息时,首先检查该消息是否已经处理过,通过数据库或缓存来判断该消息是否已经被消费。如果已经被处理,则忽略该消息。
4.RabbitMQ共有几种交换机?
12.MySQL
1.事务
1.事务的特性 ACID
2.并发事务问题
3.undo log 和redo log的区别?
4.事务的隔离性是如何保证的呢?
5.解释一下mvcc
2.索引
1.如何定位慢查询?
2.explain
3.了解过索引吗?索引的底层数据结构B树和B+树对比
4.什么是聚集索引,什么是二级索引?
5.什么是回表查询?
6.什么叫覆盖索引?
7.MySQL超大分页处理,使用覆盖索引+子查询临时表
8.索引创建的原则
9.索引失效的情况
1.违反最左前缀法则
2.范围查询时右边的列,不能使用索引
3.索引列进行运算操作,索引将失效
4.字符串不加单引号
5.头部模糊查询会导致索引失效,最后的一个元素模糊,则索引不会失效
6.or连接的条件
7.数据分布影响
is null 和is not null走不走索引,也是跟符合条件的数据多少有关,符合条件的数据多,就不走索引了
10.前缀索引
11.小结
3.锁机制
MySQL的锁分为全局锁、表级锁、行级锁
1.全局锁
2.表级锁
3.行级锁
4.总结
4.SQL优化的经验
1.SQL语句优化
2.union all 与union的区别
union all是直接将结果合并,可以包含重复数据
union 是将结果合并,并过滤掉重复数据,会多一次过滤
5.主从复制
1.使用主从复制的原因?
2.MySQL主从同步原理
13.JUC
1.synchoronized关键字
1.synchoronized是怎么实现的?
2.synchronized锁的是什么?
3.synchronized是如何保证原子性、可见性、有序性的?
4.对象的内存结构
5.synchronized锁升级的过程?
1. 无锁状态
• 对象刚创建时,对象头的 Mark Word 处于无锁状态,此时对象没有被任何线程锁定。
• Mark Word 中存储的通常是对象的哈希码、分代年龄等信息。
2. 偏向锁(Biased Locking)
• 当第一个线程尝试获取锁时,JVM 会将对象头的 Mark Word 更新为偏向锁状态,并将线程 ID 存储在 Mark Word 中。
• 偏向锁的特点是,如果同一个线程再次进入同步块,不需要做任何同步操作(例如 CAS 操作),直接执行即可,极大地提高了性能。
• 锁撤销:如果其他线程尝试获取该锁(即尝试将 Mark Word 中的线程 ID 更改为自己),偏向锁会被撤销。撤销时,会触发轻量级锁的过程。
3. 轻量级锁(Lightweight Locking)
• 锁升级到轻量级锁的条件:当偏向锁被撤销后,JVM 会使用 CAS 操作尝试将 Mark Word 的内容替换为指向线程栈中锁记录(Lock Record)的指针。如果 CAS 成功,则表示轻量级锁获取成功。
• 锁膨胀:如果多个线程反复竞争锁,导致自旋失败次数达到一定阈值,轻量级锁会膨胀为重量级锁。
4. 重量级锁(Heavyweight Locking)
• 锁升级到重量级锁的条件:当自旋失败次数过多,或者线程数过多,JVM 会将轻量级锁膨胀为重量级锁。此时,Mark Word 中的内容会变为指向Monitor 对象的指针。
7.synchronized的锁消除和锁粗化
8.synchronized和reentrantLock区别?
9.ReentrantLock的实现原理?
10.请谈谈你对volatile的理解
2.你谈谈JMM
3.CAS
1.原理简介
2.如何使用CAS手写一个自旋锁?
java
package org.example.cas;
import java.util.concurrent.atomic.AtomicReference;
/**
* Description: 手写自旋锁
* @Author sun
* @Create 2024/7/28 19:14
* @Version 1.0
*/
public class SpinLockDemo {
// 原子引用,存放线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
/**
* 加锁
*/
public void lock() throws InterruptedException {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in");
// 自旋锁,如果当前值为null,则更新为当前线程
while (!atomicReference.compareAndSet(null, thread)) {
// 休眠5s
Thread.sleep(5000);
}
}
/**
* 解锁
*/
public void unlock() {
Thread thread = Thread.currentThread();
// 如果当前值为当前线程,则更新为null
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
try {
// 将当前线程装进原子引用
spinLockDemo.lock();
// 休眠3s
Thread.sleep(3000);
// 将原子引用里的线程更新为null
spinLockDemo.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AA").start();
new Thread(() -> {
try {
// 将当前线程装进原子引用
spinLockDemo.lock();
// 休眠1s
Thread.sleep(1000);
// 将原子引用里的线程更新为null
spinLockDemo.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BB").start();
}
}
3.ABA问题
4.AtomicSampedReference解决ABA问题
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class OptimisticLockExample {
// 创建一个AtomicStampedReference,初始值为100,初始标记为0
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 0);
public static void main(String[] args) {
// 模拟一个ABA问题的场景
Thread t1 = new Thread(() -> {
int stamp = atomicStampedRef.getStamp(); // 获取初始版本号
System.out.println("Thread 1 - Initial Stamp: " + stamp);
// 获取当前的值并打印
Integer currentValue = atomicStampedRef.getReference();
System.out.println("Thread 1 - Initial Value: " + currentValue);
// 执行第一次比较交换,将值改为101
atomicStampedRef.compareAndSet(currentValue, 101, stamp, stamp + 1);
System.out.println("Thread 1 - Updated Value to 101, New Stamp: " + atomicStampedRef.getStamp());
// 模拟ABA问题,将值又改回100
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
System.out.println("Thread 1 - Reverted Value to 100, New Stamp: " + atomicStampedRef.getStamp());
});
// 另一个线程尝试更新值
Thread t2 = new Thread(() -> {
try {
// 确保线程1已经执行完第一次更新
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = atomicStampedRef.getStamp(); // 获取初始版本号
Integer currentValue = atomicStampedRef.getReference();
System.out.println("Thread 2 - Initial Stamp: " + stamp);
System.out.println("Thread 2 - Initial Value: " + currentValue);
// 尝试将值从100改为101
boolean isUpdated = atomicStampedRef.compareAndSet(currentValue, 101, stamp, stamp + 1);
System.out.println("Thread 2 - Update Successful: " + isUpdated);
System.out.println("Thread 2 - Final Value: " + atomicStampedRef.getReference());
System.out.println("Thread 2 - Final Stamp: " + atomicStampedRef.getStamp());
});
t1.start();
t2.start();
}
}
5.乐观锁和悲观锁的区别?
6.CAS是怎么在操作系统层面保证原子性的?
5.AQS
1.什么是AQS?
2.基本工作机制
3.多个线程共同去抢这个资源,是如何保证原子性的呢?
4.AQS是公平锁还是非公平锁?
5.AQS是如何实现线程的等待和唤醒的?
6.线程池
1.线程池的核心参数
2.线程池的执行原理
3.线程中有哪些常见的阻塞队列?
4.如何确定核心线程数?
5.线程池的种类有哪些?
6.为什么不建议使用Executor创建线程池?
14.线程基础知识
1.线程与进程区别?
2.并行与并发有什么区别?
3.创建线程的方式有哪些?
4.线程的run和start有什么区别?
5.线程包括哪些状态?状态之间是如何变化的?
6.新建T1,T2,T3三个线程,如何保证他们按照顺序执行
7.死锁产生的条件是什么?
8.如何进行死锁诊断呢?
9.Java怎么保证多线程的执行安全?
10.wait和sleep方法的不同?
11.如何停止一个正在运行的线程?
使用标记
使用interrupt中断线程
12.三个线程分别顺序打印0-100
java
public class ThreadPrintDemo {
private static int count = 1; // 要打印的数字
private static int threadId = 1; // 控制当前线程的 ID
private static final int MAX = 100; // 打印的最大值
public static void main(String[] args) {
// 创建三个线程,轮流打印
Thread t1 = new Thread(new PrintTask(1));
Thread t2 = new Thread(new PrintTask(2));
Thread t3 = new Thread(new PrintTask(3));
t1.start();
t2.start();
t3.start();
}
// 定义一个静态内部类实现打印任务
static class PrintTask implements Runnable {
private int id; // 当前线程的标识
public PrintTask(int id) {
this.id = id;
}
@Override
public void run() {
while (true) {
synchronized (ThreadPrintDemo.class) {
// 如果线程的 id 和控制的 threadId 不相等,当前线程等待,使用while在唤醒后再次检查条件,避免虚假唤醒
while (threadId != id) {
try {
ThreadPrintDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果当前 count 超过 100,停止打印
if (count > MAX) {
ThreadPrintDemo.class.notifyAll(); // 唤醒其他等待的线程
break;
}
// 打印当前数字
System.out.println("线程" + id + ": " + count++);
// 切换到下一个线程
threadId = threadId % 3 + 1;
// 唤醒其他线程
ThreadPrintDemo.class.notifyAll();
}
}
}
}
}