日常說到高並行往往針對共用資源進行讀寫操作很容易得到錯誤的結果,這個時候就需要應用到各種個樣的鎖,本文通過4種鎖進行分享:
public void testSynchronizedCode() {
synchronized (lockObject) {
System.out.println("同步程式碼塊");
}
}
執行:javap -verbose testSynchronized.class
public void testSynchronizedCode();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lockObject:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #9 // String 同步程式碼塊
12: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
進入程式碼塊之前先通過monitorenter獲取monitor鎖,如果程式碼塊執行過程中沒有異常通過monitorexit釋放monitor鎖,
異常則通過monitorexit鎖釋放monitor鎖
public synchronized void testSynchronizedMethod() {
System.out.println("同步方法");
}
執行:javap -verbose testSynchronized.class
public synchronized void testSynchronizedMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String 同步方法
5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 18: 0
line 19: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Llsf/study/testSynchronized;
// 引數fair 代表是否公平鎖,預設為false
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//重入鎖配合Condition進行使用實現等待功能
private final ReentrantLock reentrantLock = new ReentrantLock(true);
private final Condition addCondition = reentrantLock.newCondition();
private final Condition subCondition = reentrantLock.newCondition();
private int count = 0;
public void add() {
reentrantLock.lock();
try {
while (count >= 10) {
System.out.println("加法-等待區");
addCondition.await();
}
count++;
System.out.println("加---結果:" + count);
subCondition.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public void sub() {
reentrantLock.lock();
try {
while (count <= 0) {
System.out.println("減法-等待區");
subCondition.await();
}
count--;
System.out.println("減---結果:" + count);
addCondition.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
reentrantReadWriteLock.readLock().lock();
System.out.println("讀鎖獲取完畢");
reentrantReadWriteLock.writeLock().lock();
System.out.println("寫鎖獲取完畢");
執行結果:
讀鎖獲取完畢
ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
reentrantReadWriteLock.writeLock().lock();
System.out.println("寫鎖獲取完畢");
reentrantReadWriteLock.readLock().lock();
System.out.println("讀鎖獲取完畢");
reentrantReadWriteLock.writeLock().unlock();
System.out.println("寫鎖釋放完畢");
reentrantReadWriteLock.readLock().unlock();
System.out.println("讀鎖釋放完畢");
reentrantReadWriteLock.writeLock().lock();
System.out.println("寫鎖獲取完畢");
執行結果:
寫鎖獲取完畢
讀鎖獲取完畢
寫鎖釋放完畢
讀鎖釋放完畢
寫鎖獲取完畢
總結如下:讀讀共用、寫讀互斥,讀寫互斥、寫寫互斥
StampedLock和ReadWriteLock相比,改進讀的過程中也允許獲取寫鎖後寫入!我們讀的資料就可能不一致,
需要額外程式碼判斷讀的過程中是否有寫入,這種讀鎖是一種樂觀鎖。
通過StampedLocked類提供樣例程式碼進行分析
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead();// 嘗試獲取樂觀鎖
double currentX = x, currentY = y;
if (!sl.validate(stamp)) { //判斷讀的過程中是否有寫入,如果沒有則沒有鎖
stamp = sl.readLock(); // 獲取讀鎖
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);//嘗試轉化為寫鎖
if (ws != 0L) {
// 升級成功,更新鎖戳標,退出迴圈
stamp = ws;
x = newX;
y = newY;
break;
} else {
// 讀鎖升級寫鎖失敗,顯示獲取獨佔鎖,迴圈重試
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
ReentrantReadWriteLock其他執行緒嘗試獲取寫鎖的時候,會被阻塞,
StampedLock在樂觀獲取鎖後,其他執行緒嘗試獲取寫鎖,也不會被阻塞,這其實是對讀鎖的優化,
在獲取樂觀讀鎖後,還需要對結果進行校驗。