mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-07-28 12:22:17 +08:00
Compare commits
4 Commits
bd61ba6dbe
...
029713f39d
Author | SHA1 | Date | |
---|---|---|---|
|
029713f39d | ||
|
5357847c57 | ||
|
b9e11791a6 | ||
|
e1964178fe |
@ -70,7 +70,7 @@ inode 是 Linux/Unix 文件系统的基础。那 inode 到是什么?有什么作
|
||||
|
||||
通过以下五点可以概括 inode 到底是什么:
|
||||
|
||||
1. 硬盘的最小存储单位是扇区(Sector),块(block)由多个扇区组成。文件数据存储在块中。块的最常见的大小是 4kb,约为 8 个连续的扇区组成(每个扇区存储 512 字节)。一个文件可能会占用多个 block,但是一个块只能存放一个文件。虽然,我们将文件存储在了块(block)中,但是我们还需要一个空间来存储文件的 **元信息 metadata**:如某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等。这种 **存储文件元信息的区域就叫 inode**,译为索引节点:**i(index)+node**。 **每个文件都有一个唯一的 inode,存储文件的元信息。**
|
||||
1. 硬盘以扇区 (Sector) 为最小物理存储单位,而操作系统和文件系统以块 (Block) 为单位进行读写,块由多个扇区组成。文件数据存储在这些块中。现代硬盘扇区通常为 4KB,与一些常见块大小相同,但操作系统也支持更大的块大小,以提升大文件读写性能。文件元信息(例如权限、大小、修改时间以及数据块位置)存储在 inode(索引节点)中。每个文件都有唯一的 inode。inode 本身不存储文件数据,而是存储指向数据块的指针,操作系统通过这些指针找到并读取文件数据。 固态硬盘 (SSD) 虽然没有物理扇区,但使用逻辑块,其概念与传统硬盘的块类似。
|
||||
2. inode 是一种固定大小的数据结构,其大小在文件系统创建时就确定了,并且在文件的生命周期内保持不变。
|
||||
3. inode 的访问速度非常快,因为系统可以直接通过 inode 号码定位到文件的元数据信息,无需遍历整个文件系统。
|
||||
4. inode 的数量是有限的,每个文件系统只能包含固定数量的 inode。这意味着当文件系统中的 inode 用完时,无法再创建新的文件或目录,即使磁盘上还有可用空间。因此,在创建文件系统时,需要根据文件和目录的预期数量来合理分配 inode 的数量。
|
||||
|
@ -193,7 +193,7 @@ MySQL 中没有专门的布尔类型,而是用 TINYINT(1) 类型来表示布
|
||||
- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
|
||||
- **优化器:** 按照 MySQL 认为最优的方案去执行。
|
||||
- **执行器:** 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
|
||||
- **插件式存储引擎**:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。
|
||||
- **插件式存储引擎**:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。InnoDB 是 MySQL 的默认存储引擎,绝大部分场景使用 InnoDB 就是最好的选择。
|
||||
|
||||
## MySQL 存储引擎
|
||||
|
||||
|
@ -646,9 +646,10 @@ public class SynchronizedDemo {
|
||||
|
||||
相比`synchronized`,`ReentrantLock`增加了一些高级功能。主要来说主要有三点:
|
||||
|
||||
- **等待可中断** : `ReentrantLock`提供了一种能够中断等待锁的线程的机制,通过 `lock.lockInterruptibly()` 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
|
||||
- **等待可中断** : `ReentrantLock`提供了一种能够中断等待锁的线程的机制,通过 `lock.lockInterruptibly()` 来实现这个机制。也就是说当前线程在等待获取锁的过程中,如果其他线程中断当前线程「 `interrupt()` 」,当前线程就会抛出 `InterruptedException` 异常,可以捕捉该异常进行相应处理。
|
||||
- **可实现公平锁** : `ReentrantLock`可以指定是公平锁还是非公平锁。而`synchronized`只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。`ReentrantLock`默认情况是非公平的,可以通过 `ReentrantLock`类的`ReentrantLock(boolean fair)`构造方法来指定是否是公平的。
|
||||
- **可实现选择性通知(锁可以绑定多个条件)**: `synchronized`关键字与`wait()`和`notify()`/`notifyAll()`方法相结合可以实现等待/通知机制。`ReentrantLock`类当然也可以实现,但是需要借助于`Condition`接口与`newCondition()`方法。
|
||||
- **支持超时** :`ReentrantLock` 提供了 `tryLock(timeout)` 的方法,可以指定等待获取锁的最长等待时间,如果超过了等待时间,就会获取锁失败,不会一直等待。
|
||||
|
||||
如果你想使用上述功能,那么选择 `ReentrantLock` 是一个不错的选择。
|
||||
|
||||
@ -656,6 +657,91 @@ public class SynchronizedDemo {
|
||||
|
||||
> `Condition`是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个`Lock`对象中可以创建多个`Condition`实例(即对象监视器),**线程对象可以注册在指定的`Condition`中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用`notify()/notifyAll()`方法进行通知时,被通知的线程是由 JVM 选择的,用`ReentrantLock`类结合`Condition`实例可以实现“选择性通知”** ,这个功能非常重要,而且是 `Condition` 接口默认提供的。而`synchronized`关键字就相当于整个 `Lock` 对象中只有一个`Condition`实例,所有的线程都注册在它一个身上。如果执行`notifyAll()`方法的话就会通知所有处于等待状态的线程,这样会造成很大的效率问题。而`Condition`实例的`signalAll()`方法,只会唤醒注册在该`Condition`实例中的所有等待线程。
|
||||
|
||||
关于 **等待可中断** 的补充:
|
||||
|
||||
> `lockInterruptibly()` 会让获取锁的线程在阻塞等待的过程中可以响应中断,即当前线程在获取锁的时候,发现锁被其他线程持有,就会阻塞等待
|
||||
>
|
||||
> 在阻塞等待的过程中,如果其他线程中断当前线程 「 `interrupt()` 」,就会抛出 `InterruptedException` 异常,可以捕获该异常,做一些处理操作
|
||||
>
|
||||
> 为了更好理解这个方法,借用 Stack Overflow 上的一个案例,可以更好地理解 `lockInterruptibly()` 可以响应中断:
|
||||
>
|
||||
> ```JAVA
|
||||
> public class MyRentrantlock {
|
||||
> Thread t = new Thread() {
|
||||
> @Override
|
||||
> public void run() {
|
||||
> ReentrantLock r = new ReentrantLock();
|
||||
> // 1.1、第一次尝试获取锁,可以获取成功
|
||||
> r.lock();
|
||||
>
|
||||
> // 1.2、此时锁的重入次数为 1
|
||||
> System.out.println("lock() : lock count :" + r.getHoldCount());
|
||||
>
|
||||
> // 2、中断当前线程,通过 Thread.currentThread().isInterrupted() 可以看到当前线程的中断状态为 true
|
||||
> interrupt();
|
||||
> System.out.println("Current thread is intrupted");
|
||||
>
|
||||
> // 3.1、尝试获取锁,可以成功获取
|
||||
> r.tryLock();
|
||||
> // 3.2、此时锁的重入次数为 2
|
||||
> System.out.println("tryLock() on intrupted thread lock count :" + r.getHoldCount());
|
||||
> try {
|
||||
> // 4、打印线程的中断状态为 true,那么调用 lockInterruptibly() 方法就会抛出 InterruptedException 异常
|
||||
> System.out.println("Current Thread isInterrupted:" + Thread.currentThread().isInterrupted());
|
||||
> r.lockInterruptibly();
|
||||
> System.out.println("lockInterruptibly() --NOt executable statement" + r.getHoldCount());
|
||||
> } catch (InterruptedException e) {
|
||||
> r.lock();
|
||||
> System.out.println("Error");
|
||||
> } finally {
|
||||
> r.unlock();
|
||||
> }
|
||||
>
|
||||
> // 5、打印锁的重入次数,可以发现 lockInterruptibly() 方法并没有成功获取到锁
|
||||
> System.out.println("lockInterruptibly() not able to Acqurie lock: lock count :" + r.getHoldCount());
|
||||
>
|
||||
> r.unlock();
|
||||
> System.out.println("lock count :" + r.getHoldCount());
|
||||
> r.unlock();
|
||||
> System.out.println("lock count :" + r.getHoldCount());
|
||||
> }
|
||||
> };
|
||||
> public static void main(String str[]) {
|
||||
> MyRentrantlock m = new MyRentrantlock();
|
||||
> m.t.start();
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> 输出:
|
||||
>
|
||||
> ```BASH
|
||||
> lock() : lock count :1
|
||||
> Current thread is intrupted
|
||||
> tryLock() on intrupted thread lock count :2
|
||||
> Current Thread isInterrupted:true
|
||||
> Error
|
||||
> lockInterruptibly() not able to Acqurie lock: lock count :2
|
||||
> lock count :1
|
||||
> lock count :0
|
||||
> ```
|
||||
|
||||
关于 **支持超时** 的补充:
|
||||
|
||||
> **为什么需要 `tryLock(timeout)` 这个功能呢?**
|
||||
>
|
||||
> 假设这样一种场景:有一个加载缓存数据的任务在某个时间点多个线程同时要来执行,为了并发安全,通过锁来控制只有一个线程可以执行该任务。
|
||||
>
|
||||
> 假设大量线程同时来执行该任务,由于需要穿行执行,因此大量线程都进入阻塞队列等待获取锁
|
||||
>
|
||||
> 当第一个线程拿到锁,执行完任务之后,此时后边的线程都不需要执行该任务了,但是由于没有这个超时功能,导致后边的线程还需要在队列中阻塞等待获取锁,再一个个进入同步代码块,发现任务已经执行过了,不需要自己再执行了,之后再退出释放锁,退出同步代码块。
|
||||
>
|
||||
> 因此就需要一个支持超时的功能,`tryLock(timeout)` 的作用就是 **将大量线程的串行操作转为并行操作** ,当大量线程等待时间已经超过了指定的超时时间,直接返回 false,表示获取锁失败,不需要大量的线程串行排队等待获取锁。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 这里 `tryLock(timeout)` 的情况只是举一个特殊的情况,其实是参考了分布式环境下,更新 Redis 缓存时会出现这种情况,但是在分布式环境下肯定不会使用 synchronized ,因此这里主要是举个例子说一下 tryLock(timeout) 的作用!
|
||||
|
||||
### 可中断锁和不可中断锁有什么区别?
|
||||
|
||||
- **可中断锁**:获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。`ReentrantLock` 就属于是可中断锁。
|
||||
|
@ -54,6 +54,12 @@ category: 代码质量
|
||||
|
||||
> 重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。
|
||||
|
||||
## 性能优化就是重构吗?
|
||||
|
||||
重构的目的是提高代码的可读性、可维护性和灵活性,它关注的是代码的内部结构——如何让开发者更容易理解代码,如何让后续的功能开发和维护更加高效。而性能优化则是为了让代码运行得更快、占用更少的资源,它关注的是程序的外部表现——如何减少响应时间、降低资源消耗、提升系统吞吐量。这两者看似对立,但实际上它们的目标是统一的,都是为了提高软件的整体质量。
|
||||
|
||||
在实际开发中,理想的做法是首先**确保代码的可读性和可维护性**,然后根据实际需求选择合适的性能优化手段。优秀的软件设计不是一味追求性能最大化,而是要在可维护性和性能之间找到平衡。通过这种方式,我们可以打造既**易于管理**又具有**良好性能**的软件系统。
|
||||
|
||||
## 何时进行重构?
|
||||
|
||||
重构在是开发过程中随时可以进行的,见机行事即可,并不需要单独分配一两天的时间专门用来重构。
|
||||
@ -132,6 +138,7 @@ Code Review 可以非常有效提高代码的整体质量,它会帮助我们
|
||||
|
||||
除了可以在重构项目代码的过程中练习精进重构之外,你还可以有下面这些手段:
|
||||
|
||||
- [当我重构时,我在想些什么](https://mp.weixin.qq.com/s/pFaFKMXzNCOuW2SD9Co40g):转转技术的这篇文章总结了常见的重构场景和重构方式。
|
||||
- [重构实战练习](https://linesh.gitbook.io/refactoring/):通过几个小案例一步一步带你学习重构!
|
||||
- [设计模式+重构学习网站](https://refactoringguru.cn/):免费在线学习代码重构、 设计模式、 SOLID 原则 (单一职责、 开闭原则、 里氏替换、 接口隔离以及依赖反转) 。
|
||||
- [IDEA 官方文档的代码重构教程](https://www.jetbrains.com/help/idea/refactoring-source-code.html#popular-refactorings):教你如何使用 IDEA 进行重构。
|
||||
|
Loading…
x
Reference in New Issue
Block a user