* ## **重入鎖**
重入鎖也稱遞歸鎖,指的是同一線程外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但卻不會受到影響,ReentrantLock 和synchronized都是可重入鎖。
```
class TaskThread implements Runnable {
public synchronized void set() {
System.out.println(Thread.currentThread().getName() + ":set");
}
public synchronized void get() {
System.out.println(Thread.currentThread().getName() + ":get");
set();
}
@Override
public void run() {
get();
}
}
class test {
public static void main(String[] args) {
TaskThread taskThread = new TaskThread();
Thread t1 = new Thread(taskThread);
Thread t2 = new Thread(taskThread);
t1.start();
t2.start();
}
}
```
執行結果
```
Thread-0:get
Thread-0:set
Thread-1:get
Thread-1:set
```
get方法中調用set方法,2個方法都加入了synchronized鎖,但是并沒有產生死鎖的現象。說明set方法能獲取到上層的get的鎖。
* ## **讀寫鎖**
ReentrantLock和synchronized都為排它鎖,在同一時刻只允許一個線程訪問鎖資源,而讀寫鎖在同一時刻可以允許多個讀線程訪問,在寫線程訪問的時候其他的讀線程和寫線程都會被阻塞,讀寫鎖維護一對鎖(讀鎖和寫鎖),通過鎖的分離,使得并發性提高,適用于讀多寫少的應用場景。
```
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 讀鎖
Lock r = rwl.readLock();
// 寫鎖
Lock w = rwl.writeLock();
```
* ## **悲觀鎖與樂觀鎖**
### 樂觀鎖
不會上鎖的一種鎖,通常采用添加version版本字段的方式,每次修改前都會對比版本號,修改后version+1
### 悲觀鎖
每次讀取數據都認為其他線程會修改數據,所以都會加鎖,synchronized也算是悲觀鎖,還有數據庫的行鎖,讀鎖,寫鎖也都是悲觀鎖。
* ## **原子類**
線程安全的3大特性之一就是原子性,要想保證線程原子性,我們通常會采用加入synchronized的方式。但是synchronized鎖會產生阻塞,必定會影響程序性能。對于一些相對標準化的共享變量的多線程操作,比如i++等,java并發包提供了一種更加有效率的方式.。
```
private AtomicInteger count = new AtomicInteger();
...
count.incrementAndGet()
...
```
通過創建AtomicInteger對象,調用incrementAndGet方法就能實現線程安全的非阻塞式的原子操作,java.util.concurrent.atomic原子操作工具包中還提供了很多其他類型的操作做。
```
AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
```
這些原子類中都是用了無鎖的概念,大多使用CAS無鎖機制來實現底層原理。
* ## **CAS無鎖機制**
CAS是英文單詞**Compare And Swap**的縮寫,翻譯過來就是比較并替換。
CAS的三個核心參數CAS(V,E,N):
```
1. 內存地址V:共享變量的內存地址,jmm模型中可以理解為共享變量存放在主內存中值。
2. 預期值E:jmm模型中可以理解為保存在線程本地緩存中的值。
3. 新值N:線程經過運算后,即將要寫入到主內存中的值。
```
CAS機制中,更新一個變量的時候,只有當變量的預期值E和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為N。舉個例子:
```
1.在內存地址V當中,存儲著值為5的變量。
2.此時線程1想要把變量的值增加1。對線程1來說,預期值E=5,要修改的新值N=6。
3.但是,在線程1要提交更新之前,另一個線程2把內存地址V中的變量值率先更新成了6。
4.此時,線程1開始提交更新,首先進行E和地址V的實際值比較(Compare),發現E不等于V的實際值,提交失敗。
5.線程1重新獲取內存地址V的當前值,并重新計算想要修改的新值。此時對線程1來說,E=6,N=7。
6.這一次比較幸運,沒有其他線程改變地址V的值。線程1進行Compare,發現E和地址V的實際值是相等的。
7.線程1進行替換,把地址V的值替換為N,也就是7。
```
知道了CAS的執行機制,再來分析`AtomicInteger
`的源碼
```
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 用來實現CAS機制的實例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 共享變量在主內存中偏移量,可以理解是V值
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
...
// 自增的方法
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}
```
再來看`getAndAddInt`方法的內部實現
```
// 原生方法實現CAS算法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
// 原生方法獲取期望值E
public native int getIntVolatile(Object var1, long var2);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 調用原生方法獲取期望值E
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
```
代碼可以理解為
```
do {
獲取期望值E
} while(!CAS(內存地址V, 期望值E, 新值N));
return 期望值E;
```
CAS機制的缺點
問題:如果變量V初次讀取的時候是5,并且在準備賦值的時候檢查到它仍然是5,那能說明它的值沒有被其他線程修改過了嗎?
如果在這段期間曾經被改成6,然后又改回5,那CAS操作就會誤認為它從來沒有被修改過。但是這對當前線程的執行會有影響嗎????
* ## **Synchronized實現原理**
未完待續,貌似挺復雜
* ## **Lock實現原理與AQS**
未完待續,貌似挺復雜
* ## **自旋鎖**
自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那里,會持續占用CPU資源,自旋鎖適用于鎖使用者保持鎖時間比較短的情況。CAS屬于自旋鎖模式
* ## **Disruptor并發框架**
能夠在一個線程里每秒處理6百萬訂單的開源并發框架。附上鏈接
[http://ifeve.com/disruptor/](http://ifeve.com/disruptor/)