1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]数据库并发一致性问题添加配图帮助理解

This commit is contained in:
guide 2022-11-19 11:48:56 +08:00
parent c338579e36
commit 4132cc2bd4
7 changed files with 66 additions and 29 deletions

View File

@ -6,6 +6,7 @@ export default defineUserConfig({
dest: "./dist", dest: "./dist",
theme: themeConfig, theme: themeConfig,
shouldPrefetch: false, shouldPrefetch: false,
title: "JavaGuide(Java面试+学习指南)",
description: description:
"「Java学习指北+Java面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,复习 Java 知识点,首选 JavaGuide ", "「Java学习指北+Java面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,复习 Java 知识点,首选 JavaGuide ",
head: [ head: [

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -39,7 +39,7 @@ MySQL、PostgreSQL、Oracle、SQL Server、SQLite微信本地的聊天记录
## MySQL 基础架构 ## MySQL 基础架构
> 建议配合 [SQL语句在MySQL中的执行过程](./how-sql-executed-in-mysql.md) 这篇文章来理解 MySQL 基础架构。另外,“一个 SQL 语句在 MySQL 中的执行流程”也是面试中比较常问的一个问题。 > 建议配合 [SQL 语句在 MySQL 中的执行过程](./how-sql-executed-in-mysql.md) 这篇文章来理解 MySQL 基础架构。另外,“一个 SQL 语句在 MySQL 中的执行流程”也是面试中比较常问的一个问题。
下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。 下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。
@ -179,7 +179,7 @@ InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM
## MySQL 索引 ## MySQL 索引
MySQL 索引相关的问题比较多,对于面试和工作都比较重要,于是,我单独抽了一篇文章专门来总结 MySQL 索引相关的知识点和问题: [MySQL索引详解](./mysql-index.md) 。 MySQL 索引相关的问题比较多,对于面试和工作都比较重要,于是,我单独抽了一篇文章专门来总结 MySQL 索引相关的知识点和问题: [MySQL 索引详解](./mysql-index.md) 。
## MySQL 查询缓存 ## MySQL 查询缓存
@ -284,12 +284,39 @@ COMMIT;
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
- **脏读Dirty read:** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。 #### 脏读Dirty read
- **丢失修改Lost to modify:** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20事务 2 也读取 A=20事务 1 修改 A=A-1事务 2 也修改 A=A-1最终结果 A=19事务 1 的修改被丢失。
- **不可重复读Unrepeatable read:** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- **幻读Phantom read:** 幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。
**不可重复读和幻读有什么区别呢?** 一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。
例如:事务 1 读取某表中的数据 A=20事务 1 修改 A=A-1事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并为提交到数据库, A 的值还是 20。
![脏读](./images/concurrency-consistency-issues-dirty-reading.png)
#### 丢失修改Lost to modify
在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
例如:事务 1 读取某表中的数据 A=20事务 2 也读取 A=20事务 1 先修改 A=A-1事务 2 后来也修改 A=A-1最终结果 A=19事务 1 的修改被丢失。
![丢失修改](./images/concurrency-consistency-issues-missing-modifications.png)
#### 不可重复读Unrepeatable read
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
例如:事务 1 读取某表中的数据 A=20事务 2 也读取 A=20事务 1 修改 A=A-1事务 2 再次读取 A =19此时读取的结果和第一次读取的结果不同。
![不可重复读](./images/concurrency-consistency-issues-unrepeatable-read.png)
#### 幻读Phantom read
幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
例如:事务 2 读取某个范围的数据,事务 1 在这个范围插入了新的数据,事务 1 再次读取这个范围的数据发现相比于第一次读取的结果多了新的数据。
![幻读](./images/concurrency-consistency-issues-phantom-read.png)
### 不可重复读和幻读有什么区别?
- 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改; - 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
- 幻读的重点在于记录新增比如多次执行同一条查询语句DQL发现查到的记录增加了。 - 幻读的重点在于记录新增比如多次执行同一条查询语句DQL发现查到的记录增加了。
@ -460,7 +487,7 @@ DELETE...
## MySQL 性能优化 ## MySQL 性能优化
关于 MySQL 性能优化的建议总结,请看这篇文章:[MySQL高性能优化规范建议总结](./mysql-high-performance-optimization-specification-recommendations.md) 。 关于 MySQL 性能优化的建议总结,请看这篇文章:[MySQL 高性能优化规范建议总结](./mysql-high-performance-optimization-specification-recommendations.md) 。
### 能用 MySQL 直接存储文件(比如图片)吗? ### 能用 MySQL 直接存储文件(比如图片)吗?
@ -470,7 +497,7 @@ DELETE...
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/mysql/oss-search.png) ![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/mysql/oss-search.png)
也可以选择自建文件存储服务实现起来也不难基于FastDFS、MinIO推荐 等开源项目就可以实现分布式文件服务。 也可以选择自建文件存储服务,实现起来也不难,基于 FastDFS、MinIO推荐 等开源项目就可以实现分布式文件服务。
**数据库只存储文件地址信息,文件由文件存储服务负责存储。** **数据库只存储文件地址信息,文件由文件存储服务负责存储。**
@ -493,8 +520,6 @@ MySQL 提供了两个方法来处理 ip 地址
![常见的 SQL 优化手段](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/javamianshizhibei/javamianshizhibei-sql-optimization.png) ![常见的 SQL 优化手段](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/javamianshizhibei/javamianshizhibei-sql-optimization.png)
## 参考 ## 参考
- 《高性能 MySQL》第 7 章 MySQL 高级特性 - 《高性能 MySQL》第 7 章 MySQL 高级特性

View File

@ -9,7 +9,7 @@ head:
content: 多线程,死锁,synchronized,ReentrantLock,volatile,ThreadLocal,线程池,CAS,AQS content: 多线程,死锁,synchronized,ReentrantLock,volatile,ThreadLocal,线程池,CAS,AQS
- - meta - - meta
- name: description - name: description
content: Java并发常见知识点和面试题总结含详细解答,希望对你有帮助! content: Java并发常见知识点和面试题总结含详细解答
--- ---
## JMM(Java Memory Model) ## JMM(Java Memory Model)
@ -174,17 +174,17 @@ public void increase() {
## synchronized 关键字 ## synchronized 关键字
### 说一说自己对于 synchronized 关键字的了解 ### synchronized 是什么?有什么用?
`synchronized` 翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 `synchronized` 是 Java 中的一个关键字,翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
在 Java 早期版本中,`synchronized` 属于 **重量级锁**,效率低下。 因为监视器锁monitor是依赖于底层的操作系统的 `Mutex Lock` 来实现的Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。 在 Java 早期版本中,`synchronized` 属于 **重量级锁**,效率低下。这是因为监视器锁monitor是依赖于底层的操作系统的 `Mutex Lock` 来实现的Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
不过,在 Java 6 之后,Java 官方对从 JVM 层面对 `synchronized` 较大优化,所以现在的 `synchronized` 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。所以,你会发现目前的话,不论是各种开源框架还是 JDK 源码都大量使用了 `synchronized` 关键字 不过,在 Java 6 之后, `synchronized` 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 `synchronized` 锁的效率提升了很多。因此, `synchronized` 还是可以在实际项目中使用的,像 JDK 源码、很多开源框架都大量使用了 `synchronized`
### 如何使用 synchronized 关键字 ### 如何使用 synchronized
synchronized 关键字最主要三种使用方式: `synchronized` 关键字的使用方式主要有下面 3 种
1. 修饰实例方法 1. 修饰实例方法
2. 修饰静态方法 2. 修饰静态方法
@ -233,15 +233,15 @@ synchronized(this) {
- `synchronized` 关键字加到实例方法上是给对象实例上锁; - `synchronized` 关键字加到实例方法上是给对象实例上锁;
- 尽量不要使用 `synchronized(String a)` 因为 JVM 中,字符串常量池具有缓存功能。 - 尽量不要使用 `synchronized(String a)` 因为 JVM 中,字符串常量池具有缓存功能。
### 构造方法可以使用 synchronized 关键字修饰么? ### 构造方法可以用 synchronized 修饰么?
先说结论:**构造方法不能使用 synchronized 关键字修饰。** 先说结论:**构造方法不能使用 synchronized 关键字修饰。**
构造方法本身就属于线程安全的,不存在同步的构造方法一说。 构造方法本身就属于线程安全的,不存在同步的构造方法一说。
### 讲一下 synchronized 关键字的底层原理 ### synchronized 底层原理了解吗?
synchronized 关键字底层原理属于 JVM 层面。 synchronized 关键字底层原理属于 JVM 层面的东西
#### synchronized 同步语句块的情况 #### synchronized 同步语句块的情况
@ -307,15 +307,15 @@ public class SynchronizedDemo2 {
🧗🏻 进阶一下:学有余力的小伙伴可以抽时间详细研究一下对象监视器 `monitor` 🧗🏻 进阶一下:学有余力的小伙伴可以抽时间详细研究一下对象监视器 `monitor`
### JDK1.6 之后的 synchronized 关键字底层做了哪些优化? ### JDK1.6 之后的 synchronized 底层做了哪些优化?
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
关于这几种优化的详细信息可以查看下面这篇文章:[Java6 及以上版本对 synchronized 的优化](https://www.cnblogs.com/wuqinglong/p/9945618.html) 关于这几种优化的详细信息可以查看下面这篇文章:[Java6 及以上版本对 synchronized 的优化](https://www.cnblogs.com/wuqinglong/p/9945618.html)
### synchronized 和 volatile 区别? ### synchronized 和 volatile 有什么区别?
`synchronized` 关键字和 `volatile` 关键字是两个互补的存在,而不是对立的存在! `synchronized` 关键字和 `volatile` 关键字是两个互补的存在,而不是对立的存在!
@ -323,15 +323,19 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
- `volatile` 关键字能保证数据的可见性,但不能保证数据的原子性。`synchronized` 关键字两者都能保证。 - `volatile` 关键字能保证数据的可见性,但不能保证数据的原子性。`synchronized` 关键字两者都能保证。
- `volatile`关键字主要用于解决变量在多个线程之间的可见性,而 `synchronized` 关键字解决的是多个线程之间访问资源的同步性。 - `volatile`关键字主要用于解决变量在多个线程之间的可见性,而 `synchronized` 关键字解决的是多个线程之间访问资源的同步性。
### synchronized 和 ReentrantLock 的区别 ### synchronized 和 ReentrantLock 有什么区别?
#### 两者都是可重入锁 #### 两者都是可重入锁
**“可重入锁”** 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1所以要等到锁的计数器下降为 0 时才能释放锁。 **“可重入锁”** 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1所以要等到锁的计数器下降为 0 时才能释放锁。
**JDK 提供的所有现成的 `Lock` 实现类,包括 `synchronized` 关键字锁都是可重入的。**
#### synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API #### synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
`synchronized` 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 `synchronized` 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。`ReentrantLock` 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 `synchronized` 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 `synchronized` 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。
`ReentrantLock` 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
#### ReentrantLock 比 synchronized 增加了一些高级功能 #### ReentrantLock 比 synchronized 增加了一些高级功能
@ -341,9 +345,16 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
- **可实现公平锁** : `ReentrantLock`可以指定是公平锁还是非公平锁。而`synchronized`只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。`ReentrantLock`默认情况是非公平的,可以通过 `ReentrantLock`类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 - **可实现公平锁** : `ReentrantLock`可以指定是公平锁还是非公平锁。而`synchronized`只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。`ReentrantLock`默认情况是非公平的,可以通过 `ReentrantLock`类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
- **可实现选择性通知(锁可以绑定多个条件)**: `synchronized`关键字与`wait()``notify()`/`notifyAll()`方法相结合可以实现等待/通知机制。`ReentrantLock`类当然也可以实现,但是需要借助于`Condition`接口与`newCondition()`方法。 - **可实现选择性通知(锁可以绑定多个条件)**: `synchronized`关键字与`wait()``notify()`/`notifyAll()`方法相结合可以实现等待/通知机制。`ReentrantLock`类当然也可以实现,但是需要借助于`Condition`接口与`newCondition()`方法。
> `Condition`是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个`Lock`对象中可以创建多个`Condition`实例(即对象监视器),**线程对象可以注册在指定的`Condition`中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用`notify()/notifyAll()`方法进行通知时,被通知的线程是由 JVM 选择的,用`ReentrantLock`类结合`Condition`实例可以实现“选择性通知”** ,这个功能非常重要,而且是 Condition 接口默认提供的。而`synchronized`关键字就相当于整个 Lock 对象中只有一个`Condition`实例,所有的线程都注册在它一个身上。如果执行`notifyAll()`方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而`Condition`实例的`signalAll()`方法 只会唤醒注册在该`Condition`实例中的所有等待线程 如果你想使用上述功能,那么选择 `ReentrantLock` 是一个不错的选择
**如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。性能已不是选择标准** 关于公平锁和非公平锁的补充:
> - **公平锁** : 锁被释放之后,先申请的线程/进程先得到锁。
> - **非公平锁** :锁被释放之后,后申请的线程/进程可能会先获取到锁,是随机或者按照其他优先级排序的。
关于 `Condition`接口的补充:
> `Condition`是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个`Lock`对象中可以创建多个`Condition`实例(即对象监视器),**线程对象可以注册在指定的`Condition`中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用`notify()/notifyAll()`方法进行通知时,被通知的线程是由 JVM 选择的,用`ReentrantLock`类结合`Condition`实例可以实现“选择性通知”** ,这个功能非常重要,而且是 `Condition` 接口默认提供的。而`synchronized`关键字就相当于整个 `Lock` 对象中只有一个`Condition`实例,所有的线程都注册在它一个身上。如果执行`notifyAll()`方法的话就会通知所有处于等待状态的线程,这样会造成很大的效率问题。而`Condition`实例的`signalAll()`方法,只会唤醒注册在该`Condition`实例中的所有等待线程。
## ThreadLocal ## ThreadLocal
@ -457,7 +468,7 @@ public class Thread implements Runnable {
```java ```java
public void set(T value) { public void set(T value) {
//获取当前请求的线程 //获取当前请求的线程
Thread t = Thread.currentThread(); Thread t = Thread.currentThread();
//取出 Thread 类内部的 threadLocals 变量(哈希表结构) //取出 Thread 类内部的 threadLocals 变量(哈希表结构)
ThreadLocalMap map = getMap(t); ThreadLocalMap map = getMap(t);