locks包結構層次
Lock 介面
方法簽名 | 描述 |
---|---|
void lock(); | 獲取鎖(不死不休) |
boolean tryLock(); | 獲取鎖(淺嘗輒止) |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; | 獲取鎖(過時不候) |
void lockInterruptibly() throws InterruptedException; | 獲取鎖(任人擺佈) |
void unlock(); | 釋放鎖 |
Condition newCondition(); |
程式碼示例:
public class GetLockDemo {
// 公平鎖
// static Lock lock =new ReentrantLock(true);
// 非公平鎖
static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 主執行緒 拿到鎖
lock.lock();
Thread thread =
new Thread(
() -> {
// 子執行緒 獲取鎖(不死不休)
System.out.println("begain to get lock...");
lock.lock();
System.out.println("succeed to get lock...");
// // 子執行緒 獲取鎖(淺嘗輒止)
// boolean result = lock.tryLock();
// System.out.println("是否獲得到鎖:" + result);
//
// // 子執行緒 獲取鎖(過時不候)
// try {
// boolean result1 = lock.tryLock(5, TimeUnit.SECONDS);
// System.out.println("是否獲得到鎖:" + result1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// // 子執行緒 獲取鎖(任人擺佈)
// try {
// System.out.println("start to get lock Interruptibly");
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// e.printStackTrace();
// System.out.println("dad asked me to stop...");
// }
});
thread.start();
Thread.sleep(10000L);
lock.unlock();
}
}
結論:
- lock() 最常用
- lockInterruptibly() 方法一般更昂貴,有的實現類可能沒有實現 lockInterruptible() 方法。只有真的需要用中斷時,才使用,使用前應看清實現類對該方法的描述。
Condition
Object中的wait(), notify(), notifyAll()方法是和synchronized配合使用的可以喚醒一個或者多個執行緒。Condition是需要與Lock配合使用的,提供多個等待集合和更精確的控制(底層是park/unpark機制);
協作方式 | 死鎖方式1 (鎖) | 死鎖方式2(先喚醒,再掛起) | 備註 |
---|---|---|---|
suspend/resume | 死鎖 | 死鎖 | 棄用 |
wait/notify | 不死鎖 | 死鎖 | 只用於synchronized關鍵字 |
park/unpark | 死鎖 | 不死鎖 | |
condition | 不死鎖 | 死鎖 |
condition程式碼示例:
public class ConditionDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread thread =
new Thread(
() -> {
lock.lock();
System.out.println("condition.await()");
try {
condition.await();
System.out.println("here i am...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
Thread.sleep(2000L);
lock.lock();
condition.signalAll();
lock.unlock();
}
}
ReetrantLock
ReentrantLock是可重入鎖,同一執行緒可以多次獲取到鎖
ReentrantLock實現原理分析
- ReentrantLock需要一個owner用來標記那個執行緒獲取到了鎖,一個count用來記錄加鎖的次數和一個waiters等待佇列用來存放沒有搶到鎖的執行緒列表
- 當有執行緒進來時,會先判斷count的值,如果count為0說明鎖沒有被佔用
- 然後通過CAS操作進行搶鎖
- 如果搶到鎖則count的值會加1,同時將owner設定為當前執行緒的引用
- 如果count不為0同時owner指向當前執行緒的引用,則將count的值加1
- 如果count不為0同時owner指向的不是當前執行緒的引用,則將執行緒放入等待佇列waiters中
- 如果CAS搶鎖失敗,則將執行緒放入等待佇列waiters中
- 當執行緒使用完鎖後,會釋放其持有的鎖,釋放鎖時會將count的值減1,如果count值為0則將owner設為null
- 如果count值不為0則會喚醒等待佇列頭部的執行緒進行搶鎖
手動實現ReentrantLock程式碼示例:
public class MyReentrantLock implements Lock {
// 標記重入次數的count值
private AtomicInteger count = new AtomicInteger(0);
// 鎖的擁有者
private AtomicReference<Thread> owner = new AtomicReference<>();
// 等待佇列
private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();
@Override
public boolean tryLock() {
// 判斷count是否為0,若count!=0,說明鎖被佔用
int ct = count.get();
if (ct != 0) {
// 判斷鎖是否被當前執行緒佔用,若被當前執行緒佔用,做重入操作,count+=1
if (owner.get() == Thread.currentThread()) {
count.set(ct + 1);
return true;
} else {
// 若不是當前執行緒佔用,互斥,搶鎖失敗,return false
return false;
}
} else {
// 若count=0, 說明鎖未被佔用,通過CAS(0,1) 來搶鎖
if (count.compareAndSet(ct, ct + 1)) {
// 若搶鎖成功,設定owner為當前執行緒的引用
owner.set(Thread.currentThread());
return true;
} else {
return false;
}
}
}
@Override
public void lock() {
// 嘗試搶鎖
if (!tryLock()) {
// 如果失敗,進入等待佇列
waiters.offer(Thread.currentThread());
// 自旋
for (; ; ) {
// 判斷是否是佇列頭部,如果是
Thread head = waiters.peek();
if (head == Thread.currentThread()) {
// 再次嘗試搶鎖
if (!tryLock()) {
// 若搶鎖失敗,掛起執行緒,繼續等待
LockSupport.park();
} else {
// 若成功,就出佇列
waiters.poll();
return;
}
} else {
// 如果不是佇列頭部,就掛起執行緒
LockSupport.park();
}
}
}
}
public boolean tryUnlock() {
// 判斷,是否是當前執行緒佔有鎖,若不是,拋異常
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
} else {
// 如果是,就將count-1 若count變為0 ,則解鎖成功
int ct = count.get();
int nextc = ct - 1;
count.set(nextc);
// 判斷count值是否為0
if (nextc == 0) {
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
}
@Override
public void unlock() {
// 嘗試釋放鎖
if (tryUnlock()) {
// 獲取佇列頭部, 如果不為null則將其喚醒
Thread thread = waiters.peek();
if (thread != null) {
LockSupport.unpark(thread);
}
}
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void lockInterruptibly() throws InterruptedException {}
@Override
public Condition newCondition() {
return null;
}
}
synchronized VS Lock
synchronized:
優點:
- 使用簡單,語義清晰,哪裡需要點哪裡
- 由JVM提供,提供了多種優化方案(鎖粗化,鎖消除,偏向鎖,輕量級鎖)
- 鎖的釋放由虛擬機器完成,不用人工干預,降低了死鎖的可能性
缺點:悲觀的排他鎖,無法實現鎖的高階功能如公平鎖,讀寫鎖等
Lock:
優點:可以實現synchronized無法實現的鎖的高階功能如公平鎖,讀寫鎖等,同時還可以實現更多的功能
缺點:需手動釋放鎖unlock,使用不當容易造成死鎖
結論: 兩者都是可重入鎖,synchronized可以類比為傻瓜相機,提供了固定的功能,而Lock可以類比為單方,可以根據需要調節所需的功能