Skip to content

Commit 3148b1e

Browse files
committed
mysql、并发章节补充
1 parent 5dbd712 commit 3148b1e

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

docs/advance/excellent-article/23-arthas-intro.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ sc -d cn.test.mobile.controller.order.OrderController
151151
classLoaderHash 18b4aac2
152152
```
153153
154-
###### 与之相应的还有sm( “Search-Method” ),查看已加载类的方法信息
154+
与之相应的还有sm( “Search-Method” ),查看已加载类的方法信息
155155
156156
查看String里的方法
157157

docs/database/mysql.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,12 @@ canal的原理如下:
11721172

11731173
如果是普通字段(没有索引/主键),那么`select ..... for update`就会加表锁。
11741174

1175+
## MySQL的binlog有几种格式?分别有什么区别?
11751176

1177+
有三种格式,statement,row和mixed。
1178+
1179+
- statement:每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
1180+
- row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。
1181+
- mixed:一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。
11761182

11771183
![](http://img.topjavaer.cn/img/20220612101342.png)

docs/java/java-concurrent.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,16 @@ interrupt() 并不能真正的中断线程,需要被调用的线程自己进
548548

549549
> 指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。Java编译器会在生成指令系列时在适当的位置会插入`内存屏障`指令来禁止处理器重排序。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。对一个volatile字段进行写操作,Java内存模型将在写操作后插入一个写屏障指令,这个指令会把之前的写入值都刷新到内存。
550550
551+
## volatile为什么不能保证原子性?
552+
553+
volatile可以保证可见性和顺序性,但是它不能保证原子性。
554+
555+
举个例子。一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。
556+
557+
假如i的初始值为100。线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也去取i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
558+
559+
那么问题来了,线程A之前已经读取到了i的值为100,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存。这样i经过两次自增之后,结果值只加了1,明显是有问题的。所以说即便volatile具有可见性,也不能保证对它修饰的变量具有原子性。
560+
551561
## synchronized的用法有哪些?
552562

553563
1. **修饰普通方法**:作用于当前对象实例,进入同步代码前要获得当前对象实例的锁
@@ -800,6 +810,23 @@ ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方
800810

801811
比如Java web应用中,每个线程有自己单独的`Session`实例,就可以使用`ThreadLocal`来实现。
802812

813+
## 什么是AQS?
814+
815+
AQS(AbstractQueuedSynchronizer)是java.util.concurrent包下的核心类,我们经常使用的ReentrantLock、CountDownLatch,都是基于AQS抽象同步式队列实现的。
816+
817+
AQS作为一个抽象类,通常是通过继承来使用的。它本身是没有同步接口的,只是定义了同步状态和同步获取和同步释放的方法。
818+
819+
JUC包下面大部分同步类,都是基于AQS的同步状态的获取与释放来实现的,然后AQS是个双向链表。
820+
821+
## 为什么AQS是双向链表而不是单向的?
822+
823+
双向链表有两个指针,一个指针指向前置节点,一个指针指向后继节点。所以,双向链表可以支持常量 O(1) 时间复杂度的情况下找到前驱节点。因此,双向链表在插入和删除操作的时候,要比单向链表简单、高效。
824+
825+
从双向链表的特性来看,AQS 使用双向链表有2个方面的原因:
826+
827+
1. 没有竞争到锁的线程加入到阻塞队列,并且阻塞等待的前提是,当前线程所在节点的前置节点是正常状态,这样设计是为了避免链表中存在异常线程导致无法唤醒后续线程的问题。所以,线程阻塞之前需要判断前置节点的状态,如果没有指针指向前置节点,就需要从 Head 节点开始遍历,性能非常低。
828+
2. 在 Lock 接口里面有一个lockInterruptibly()方法,这个方法表示处于锁阻塞的线程允许被中断。也就是说,没有竞争到锁的线程加入到同步队列等待以后,是允许外部线程通过interrupt()方法触发唤醒并中断的。这个时候,被中断的线程的状态会修改成 CANCELLED。而被标记为 CANCELLED 状态的线程,是不需要去竞争锁的,但是它仍然存在于双向链表里面。这就意味着在后续的锁竞争中,需要把这个节点从链表里面移除,否则会导致锁阻塞的线程无法被正常唤醒。在这种情况下,如果是单向链表,就需要从 Head 节点开始往下逐个遍历,找到并移除异常状态的节点。同样效率也比较低,还会导致锁唤醒的操作和遍历操作之间的竞争。
829+
803830
## AQS原理
804831

805832
AQS,`AbstractQueuedSynchronizer`,抽象队列同步器,定义了一套多线程访问共享资源的同步器框架,许多并发工具的实现都依赖于它,如常用的`ReentrantLock/Semaphore/CountDownLatch`

0 commit comments

Comments
 (0)