是Java的Web应用程序开发框架
,通过不同组件来实现松散耦合
的架构。
理解MVC的相关概念:
模型(Model):表示应用程序的数据和业务逻辑。
视图(View):负责
呈现数据给用户
。控制器(Controller):控制器接收来自用户的请求,
Spring MVC的工作流程如下:
用户发送请求到前端控制器(Front Controller),通常是通过URL访问应用程序。
前端控制器(Dispatcher Servlet)拦截请求,并将其传递给相应的处理器(Handler)。
处理器根据请求的URL找到对应的处理器映射器(Handler Mapping),确定使用哪个控制器处理请求。
控制器(Controller)接收请求,并处理业务逻辑。通常它会调用相应的服务层或数据访问层。
控制器根据请求的处理结果选择合适的视图,并将模型数据传递给视图。
视图负责呈现模型数据,生成用户可以理解的内容。它将响应返回给前端控制器。
前端控制器最终将响应返回给用户。
通过使用Spring MVC,开发人员可以更好地组织和管理Web应用程序的不同层次,实现松散耦合、易于测试和维护的代码结构。它提供了丰富的功能和灵活的扩展性,使得开发Web应用程序变得更加简便和高效。
因为它使用了一种称为分段锁(Segment)
的加锁机制
来保证多线程环境下的线程安全。具体而言,ConcurrentHashMap 将哈希表分成多个小的哈希表
,每个小哈希表被称为“段”
,每个段都有自己的锁。当一个线程访问 ConcurrentHashMap 的某个段时,只会锁住这个段,而不会锁住整个哈希表
,从而实现对不同段的访问操作之间的并发执行。
因为它不提供同步机制来保护多线程对其进行并发访问时的数据一致性
。如果多个线程同时修改 HashMap 中的内容,可能会导致数据丢失、数据错乱或死循环等问题。
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map
for (Map.Entry<KeyType, ValueType> entry : map.entrySet()) {
KeyType key = entry.getKey();
ValueType value = entry.getValue();
// 对键值对进行操作
}
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map
for (KeyType key : map.keySet()) {
ValueType value = map.get(key);
// 对键值对进行操作
}
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map
Iterator<Map.Entry<KeyType, ValueType>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<KeyType, ValueType> entry = iterator.next();
KeyType key = entry.getKey();
ValueType value = entry.getValue();
// 对键值对进行操作
}
hashSet 是基于 HashMap 实现的,它使用哈希表的方式来存储数据,并且不允许重复元素存在
。当我们向 HashSet 中添加元素时,HashSet 会通过元素的 hashCode 方法计算该元素的哈希值
,并将元素存储到对应的哈希桶中。如果哈希桶中已经存在相同哈希值的元素,那么 HashSet 会使用 equals 方法判断两个元素是否相等。如果相等,HashSet 将不会添加重复的元素。
- 无序性。
- 不允许重复元素。
- 基于哈希表实现:HashSet 内部使用 HashMap 实现,每个元素都是作为 HashMap 的键存储在哈希桶中,而值则是一个常量。
- 高效的插入、删除和查找操作:基于哈希表实现的,所以具有高效性能。这些操作的时间复杂度为 O(1)。
- 允许使用 null 元素。
- 非线程安全:可以通过 Collections工具类提供的
synchronizedSet
方法来获取一个线程安全的 HashSet。
什么是哈希表?
StringBuffer和StringBuilder:StringBuffer和StringBuilder类用于可变的字符串操作。StringBuffer是线程安全的,可以在多个线程中使用,而StringBuilder则不是线程安全的。
Vector:Vector是一个动态数组,类似于ArrayList,但它是线程安全的。多个线程可以同时对Vector进行读取和写入操作。
Hashtable:Hashtable是一个传统的哈希表实现,它是线程安全的。它提供了基本的键值对存储和检索,多个线程可以同时对Hashtable进行操作,但性能上相对较差。
ConcurrentHashMap:ConcurrentHashMap是Java 5引入的线程安全的哈希表实现。它在多线程环境下提供了更好的性能,允许多个线程同时读取和更新其中的数据。
ConcurrentLinkedQueue:ConcurrentLinkedQueue是一个线程安全的无界队列实现。它适用于多个线程同时进行元素插入和删除操作的场景。
AtomicInteger和AtomicLong:AtomicInteger和AtomicLong是线程安全的整型和长整型原子变量类。它们提供了一系列的原子操作方法,可以在多线程环境下安全地进行数值更新操作。
因为:都是用于处理可变字符串的类,类中的方法用synchronized关键词修饰
,确保操作的原子性和顺序性,如果多个线程同时对同一个StringBuilder实例进行操作,就会导致竞态条件和数据不一致的问题。
由:类加载器、执行引擎、运行时数据区域、方法区、本地方法接口、类文件格式
类加载器:负责加载 Java 字节码文件(.class 文件)。
执行引擎:负责执行 JVM 中的字节码指令。它有两种主要的执行方式:解释执行和即时编译执行。解释执行逐条解释执行字节码指令,效率较低;即时编译执行将整个字节码转换为本地机器代码,以提高执行效率。
运行时数据区域:包含了 JVM 在执行程序时所需的各种内存区域。
本地方法接口:提供了与 Native 代码(非Java语言编写的代码)进行交互的接口,在 Java 代码中可以调用 Native 方法。
类文件格式:约定了 Java 字节码文件的结构,包括常量池、方法区、字段信息等。
通俗的讲:如果索引的列在 select 所需获得的列中或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。
参考:
在Mysql中,什么是回表,什么是覆盖索引,索引下推?
所需的列都包含在索引中,无需回表操作即可获取所有需要的数据,速度更快。
参考:
在Mysql中,什么是回表,什么是覆盖索引,索引下推?
Nacos的心跳机制是指在分布式环境中,Nacos通过定期发送心跳消息来检测实例的存活状态。
Nacos具备服务注册与发现
、配置管理
、服务健康监测
、动态路由与负载均衡
、服务配置共享
、集群部署与高可用性
、权限管理
等功能。
public class MainThread {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// 子线程1的任务逻辑
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// 子线程2的任务逻辑
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
}
}
import java.util.concurrent.CountDownLatch;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// 子线程1的任务逻辑
latch.countDown();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// 子线程2的任务逻辑
latch.countDown();
}
});
thread1.start();
thread2.start();
latch.await();
// 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
@Override
public void run() {
// 子线程1的任务逻辑
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
// 子线程2的任务逻辑
}
});
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
// 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
}
}
如果一个源文件中包含多个被public修饰的类,那么就无法通过文件名与类名进行唯一的对应,导致编译器无法确定如何生成字节码文件
private
:类内部成员
default
:同一包,类内部成员
protected
:同一包,子类,类内部成员
public
:任意类
基本数据类型
有8个,可以分为4个小类,分别是整数类型
(byte/short/int/long)、浮点类型
(float/double)、字符类型
(char)、布尔类型
(boolean)
1字节 (8位):byte(0), boolean(false)
2字节 (16位):short(0), char(‘\u0000’)
4字节 (32位):in(0)t, float(0.0L)
8字节 (64位):long(0L), double(0.0)
因为方法区用于存储类的结构信息、常量池、静态变量等数据。由于静态变量属于类本身,不是对象的一部分,未被static修饰的成员变量属于对象级别的变量,在对象被创建时,它们会被存储在堆内存中的对象实例中
因为变量作用域受限于声明位置,强调封装和对象独立性。
包装类的存在是为了将基本数据类型转换为对象类型,以便进行更多的操作和满足特定的编程需求。
自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型;
自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型;
可以大大简化基本类型变量和包装类对象之间的转换过程