Skip to content

Commit 272ddae

Browse files
committed
📝 Writing docs.
1 parent 72c9f58 commit 272ddae

2 files changed

Lines changed: 92 additions & 12 deletions

File tree

docs/concurrent/Java锁.md

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@ tags:
1616
<!-- TOC depthFrom:2 depthTo:4 -->
1717

1818
- [概述](#概述)
19-
- [为什么会出现 Lock?](#为什么会出现-lock)
20-
- [synchronized](#synchronized)
19+
- [概念](#概念)
20+
- [公平锁/非公平锁](#公平锁非公平锁)
21+
- [可重入锁](#可重入锁)
22+
- [独享锁/共享锁](#独享锁共享锁)
23+
- [互斥锁/读写锁](#互斥锁读写锁)
24+
- [乐观锁/悲观锁](#乐观锁悲观锁)
25+
- [分段锁](#分段锁)
26+
- [偏向锁/轻量级锁/重量级锁](#偏向锁轻量级锁重量级锁)
27+
- [自旋锁](#自旋锁)
28+
- [为什么用 Lock、ReadWriteLock](#为什么用-lockreadwritelock)
2129
- [Lock 和 ReentrantLock](#lock-和-reentrantlock)
2230
- [要点](#要点)
2331
- [源码](#源码)
@@ -47,22 +55,91 @@ tags:
4755

4856
## 概述
4957

50-
### 为什么会出现 Lock?
58+
### 概念
5159

52-
### synchronized
60+
#### 公平锁/非公平锁
5361

54-
synchronized 是 Java 的内置锁
62+
公平锁是指多个线程按照申请锁的顺序来获取锁
5563

56-
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
64+
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
5765

58-
1. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
59-
2. 线程执行发生异常,此时 JVM 会让线程自动释放锁。
66+
对于 Java `ReentrantLock`而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
6067

61-
如果这个获取锁的线程被阻塞了,但是又没有释放锁,其他线程就只能一直等待
68+
对于`Synchronized`而言,也是一种非公平锁。由于其并不像`ReentrantLock`是通过 AQS 的来实现线程调度,所以并没有任何办法使其变成公平锁
6269

63-
使用 synchronized,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
70+
#### 可重入锁
6471

65-
可重入锁可中断锁公平锁读写锁
72+
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
73+
74+
说的有点抽象,下面会有一个代码的示例。对于 Java `ReentrantLock`而言, 他的名字就可以看出是一个可重入锁,其名字是`Re entrant Lock`重新进入锁。对于`Synchronized`而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
75+
76+
```
77+
synchronized void setA() throws Exception{
78+
Thread.sleep(1000);
79+
setB();
80+
}
81+
82+
synchronized void setB() throws Exception{
83+
Thread.sleep(1000);
84+
}
85+
```
86+
87+
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB 可能不会被当前线程执行,可能造成死锁。
88+
89+
#### 独享锁/共享锁
90+
91+
独享锁是指该锁一次只能被一个线程所持有。
92+
93+
共享锁是指该锁可被多个线程所持有。
94+
95+
对于 Java `ReentrantLock`而言,其是独享锁。但是对于 Lock 的另一个实现类`ReadWriteLock`,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过 AQS 来实现的,通过实现不同的方法,来实现独享或者共享。对于`Synchronized`而言,当然是独享锁。
96+
97+
#### 互斥锁/读写锁
98+
99+
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在 Java 中的具体实现就是`ReentrantLock`
100+
读写锁在 Java 中的具体实现就是`ReadWriteLock`
101+
102+
#### 乐观锁/悲观锁
103+
104+
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
105+
106+
从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。悲观锁在 Java 中的使用,就是利用各种锁。乐观锁在 Java 中的使用,是无锁编程,常常采用的是 CAS 算法,典型的例子就是原子类,通过 CAS 自旋实现原子操作的更新。
107+
108+
#### 分段锁
109+
110+
分段锁其实是一种锁的设计,并不是具体的一种锁,对于`ConcurrentHashMap`而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。我们以`ConcurrentHashMap`来说一下分段锁的含义以及设计思想,`ConcurrentHashMap`中的分段锁称为 Segment,它即类似于 HashMap(JDK7 与 JDK8 中 HashMap 的实现)的结构,即内部拥有一个 Entry 数组,数组中的每个元素既是一个链表;同时又是一个 ReentrantLock(Segment 继承了 ReentrantLock)。当需要 put 元素的时候,并不是对整个 hashmap 进行加锁,而是先通过 hashcode 来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程 put 的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计 size 的时候,可就是获取 hashmap 全局信息的时候,就需要获取所有的分段锁才能统计。分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
111+
112+
#### 偏向锁/轻量级锁/重量级锁
113+
114+
这三种锁是指锁的状态,并且是针对`Synchronized`。在 Java 5 通过引入锁升级的机制来实现高效`Synchronized`
115+
116+
这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
117+
118+
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
119+
120+
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
121+
122+
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
123+
124+
#### 自旋锁
125+
126+
在 Java 中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。
127+
128+
### 为什么用 Lock、ReadWriteLock
129+
130+
* synchronized 的缺陷
131+
132+
* 被 synchronized 修饰的方法或代码块,只能被一个线程访问。如果这个线程被阻塞,其他线程也只能等待。
133+
* synchronized 不能响应中断。
134+
* synchronized 没有超时机制。
135+
* synchronized 只能是非公平锁。
136+
137+
* Lock、ReadWriteLock 相较于 synchronized,解决了以上的缺陷:
138+
* Lock 可以手动释放锁(synchronized 获取锁和释放锁都是自动的),以避免死锁。
139+
* Lock 可以响应中断
140+
* Lock 可以设置超时时间,避免一致等待
141+
* Lock 可以选择公平锁或非公平锁两种模式
142+
* ReadWriteLock 将读写锁分离,从而使读写操作分开,有效提高并发性。
66143

67144
## Lock 和 ReentrantLock
68145

@@ -250,7 +327,7 @@ public class ReentrantReadWriteLockDemo {
250327

251328
## AQS
252329

253-
> AQS 作为构建锁或者其他同步组件的基础框架
330+
> AQS 作为构建锁或者其他同步组件的基础框架,有必要好好了解一下其原理。
254331
255332
### 要点
256333

@@ -769,3 +846,5 @@ tryAcquireSharedNanos 方法与 tryAcquireNanos 几乎一致,不再赘述。
769846
* https://zhuanlan.zhihu.com/p/27134110
770847
* https://t.hao0.me/java/2016/04/01/aqs.html
771848
* http://ju.outofmemory.cn/entry/353762
849+
* https://blog.csdn.net/u012403290/article/details/64910926?locationNum=11&fps=1
850+
* https://www.cnblogs.com/qifengshi/p/6831055.html

docs/concurrent/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 目录
44

55
* [Java 并发容器](Java并发容器.md)
6+
* [Java 锁](Java锁.md)
67
* [Java 并发工具类](Java并发工具类.md)
78
* [Java 并发面试](Java并发面试.md)
89
* [并发术语](并发术语.md)

0 commit comments

Comments
 (0)