1.java中的List怎么实现线程安全
1.使用synchronizedList:它可以将一个普通的lIst包装为线程安全的List
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
2.CopyOnWriteArrayList:适合读多写少的场景,在修改操作时会创建一个新的数组副本。
当多个线程来执行修改操作时:每个线程都会在自己的副本数组上执行修改操作,
互不干扰,当修改完成后会替换原始数组,这个操作是原子的,确保了数据的
一致性,当其它线程发现数组已经改变了,就会重新复制副本,继续执行之前的
修改操作。
3.使用锁:在修改操作之前加锁,在完成后释放锁。可以使用synchronized或Reentrantlock
2.使用spring AOP去打印日志和拦截器去打印日志的区别
1.AOP:具有更强的解耦能力,将日志逻辑和业务逻辑分开。
可以通过在配置中指定切点来选择性的进行日志打印,从而在需要的地方添加日志,而不必在每个方法
中重复添加日志代码
2.拦截器:拦截器适用于请求处理的全局控制,不仅限于方法调用。因此,可以在请求处理的各个阶段添加日志,
例如在请求进入时、处理中间、响应返回时等。
拦截器常用于web请求处理,对于方法调用等逻辑业务可能不太适合
3.总的来说,AOP适合在方法调用层面上进行横切关注点处理,而拦截器适用于更广泛的请求处理环境
3.适配器模式的实现方式
将已经存在的类的接口,转换成需要的类的接口
1.类适配器模式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件
2.对象适配器模式:适配器类不用再去继承是配置类,而是将适配者类聚合在适配器类中
4.简单工厂模式、工厂方法模式和抽象工厂模式的区别
1.简单工厂:通过一个工厂类来创建对象,客户端不需要直接实例化具体的对象,而是通过调用工厂方法来获取
所需要的对象
2.工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪个对象
3.抽象工厂:每个具体工厂负责创建一个产品族的对象
5.接口和抽象类有哪些区别。什么时候使用抽象类、什么时候使用接口
接口:1.一个类可以实现一个或多个接口,通过实现接口中定义的方法来达到多态性的目的
2.接口中只能包含未实现的方法
4.接口中的方法和变量之类的不需要public private等修饰符的修饰
4.适用于描述一组相关的行为,实现类需要遵循相同的方法约定
抽象类:1.一个类只能继承一个抽象类
2.抽象类中可以有抽象方法也可以有普通方法
3.不能被实例化,只能被继承
4.适用于一些具有共同实现的类,并且这些类之间可能存在一个通用的行为,但又需要保留一些方法的
实现,留给子类进行具体实现
区分:
1.接口:当需要定义一个约定,以便多个类共同遵循,而不关心它们的实现细节时。
多个不相关的类需要实现相同的行为时。
2.抽象类:当需要为多个相关类提供一些通用的实现代码,但又要求子类提供特定的实现时。
抽象类可以包含实现方法,这样子类可以继承通用实现并修改。
6.spring事务的底层是怎么实现的
spring通过AOP和代理模式来实现事务的底层支持
1.代理模式:spring使用代理模式来在方法调用前后插入额外的逻辑,来实现事务管理。
2.事务管理器:spring提供了事务管理器接口,用于同意管理事务。不同的数据源和事务管理需求可以实现这个
接口,以适应不同的数据库和事务模式。
3.事务传播机制:sprign定义了不同的事务传播机制
4.事务隔离级别:spring允许配置不同的事务隔离级别
5.事务回滚策略:spring允许根据特定的异常类型来决定是否回滚事务
6.事务的开启:当标记了@Transactional注解的方法被调用时,spring会通过AOP创建一个事务代理。这个代理
会在方法执行前开启一个数据库连接,并将其与当前线程绑定。开启连接的过程可能涉及到连接池
的管理,以提高性能和资源利用率。
7.数据库操作:在方法内部执行数据库操作,如SQL语句的执行,这些操作会在之前开启的数据库连接上执行,
确保所有操作在同一个连接下完成
8.事务的提交:如果方法成功执行完成,代理会调用事务管理器的提交方法。事务管理器将确保之前的数据库
操作都在同一个事务中提交,以确保数据的一致性。提交事务的过程可能会涉及到数据库引擎的
处理,确保数据的持久性
9.事务的回滚:如果方法在执行过程中抛出了异常,或者事务管理器检测到回滚标志,代理会调用事务管理器的
回滚方法。事务管理器将撤销之前的数据库操作,将数据库回滚到事务开启时的状态,从而保证
数据的一致性和完整性
10.数据库事务的底层机制:
开启事务
执行SQL
记录日志:在执行每个修改操作之后,数据库会将相应的日志记录下来,包括操作类型、表名、行ID和修改前后的 数据
事务的提交或回滚:如果没有出现问题就提交,如果出现问题,数据库会回滚事务,将之前的操作全部撤销
持久性:事务一旦提交,数据库会确保将修改后的数据持久化的存储在磁盘上
7.ACID
原子性:指的是一个事务中的所有操作要么全部成功,要么全部失败回滚。
数据库通过记录事务的日志来实现原子性。当事务开始时,DBMS将开始记录日志,记录每个操作的变更。
如果事务失败,DBMS可以使用日志来撤销之前的操作,将数据库回滚到事务开始前的状态
一致性:确保事务在完成后,数据库从一个一致的状态转换到另一个一致的状态。事务的操作应该遵循事先定义
好的业务规则和约束,以保证数据的合法性和完整性
隔离性:指多个并发事务之间的操作是相互隔离的,一个事务的操作不应该影响其它事务的操作。数据库通过
多版本并发控制来实现隔离性。MVCC允许事务在读取数据时不被其他事务的写操作干扰,从而避免了
脏读、不可重复读和幻读问题
持久性:确保一旦提交事务,其结果将永久保存在数据库中,即使发生系统崩溃或其他规章也不会丢失。
数据库通过将事务的变更写入磁盘来实现持久性。通常,数据库在事务提交后,会将相关数据库写入
到事务日志中,以确保数据的持久化
8.存储int类型的LRU
public class LRUCache {
private final int capacity;
private final Map<Integer,Node> map;
private Node head;
private Node tail;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
head = new Node(-1,-1);
tail = new Node(-1,-1);
head.next = tail;
tail.prev = head;
}
private void addNode(Node node) {
Node next = head.next;
head.next = node;
node.prev = head;
node.next = next;
next.prev = node;
}
private void removeNode(Node node) {
Node prev = node.prev;
Node next = node.next;
prev.next = next;
next.prev = prev;
}
private int get(int key){
if (map.containsKey(key)){
Node node = map.get(key);
removeNode(node);
addNode(node);
return node.value;
}
return -1;
}
private void put(int key,int value){
if (map.containsKey(key)){
Node node = map.get(key);
removeNode(node);
node.value = value;
addNode(node);
} else {
if (map.size() >= capacity){
Node remove = tail.prev;
removeNode(remove);
map.remove(remove.key);
}
Node node = new Node(key, value);
map.put(key,node);
addNode(node);
}
}
}
class Node {
public int key;
public int value;
public Node prev;
public Node next;
public Node(int key,int value){
this.key = key;
this.value = value;
}
}
9.存储任意类型的LRU
public class LRUCache<K,V> {
private final int capacity;
private final Map<K,Node<K,V>> map;
private Node<K,V> head;
private Node<K,V> tail;
public LRUCache(int getCapacity){
this.capacity = getCapacity;
map = new HashMap<>();
head = new Node<>(null,null);
tail = new Node<>(null,null);
head.next = tail;
tail.prev = head;
}
private void addNode(Node<K,V> node){
Node<K,V> next = head.next;
head.next = node;
node.prev = head;
node.next = next;
next.prev = head;
}
private void removeNode(Node<K,V> node){
Node<K,V> prev = node.prev;
Node<K,V> next = node.next;
prev.next = next;
next.prev = prev;
}
public V get(K key){
if (map.containsKey(key)){
Node<K,V> node = map.get(key);
removeNode(node);
addNode(node);
return node.value;
}
return null;
}
public void put(K key,V value){
if (map.containsKey(key)){
Node<K,V> node = map.get(key);
node.value = value;
removeNode(node);
addNode(node);
} else {
if (map.size() >= capacity){
Node<K,V> delete = tail.prev;
removeNode(delete);
map.remove(delete.key);
} else {
Node<K,V> node = new Node<>(key,value);
map.put(key,node);
addNode(node);
}
}
}
}
class Node<K,V> {
public K key;
public V value;
public Node<K,V> prev;
public Node<K,V> next;
public Node(K key,V value){
this.key = key;
this.value = value;
}
}