之前寫了一下synchronized關鍵字的一點東西,那麼除了synchronized可以加鎖外,JUC(java.util.concurrent)提供的Lock接口也可以實現加鎖解鎖的功能。
看完本文,希望您可以瞭解或者掌握:
1:Lock接口的實現
2:Condition的原理和概念
3:ReentrantLock的實現原理,可以手寫一個簡單的ReentrantLock
4:ReadWriteLock的概念和實現原理,可以手寫一個簡單的ReadWriteLock
5:能夠瞭解到模板模型,AQS
鎖的本質:
1:加鎖,其實就是加了一種權限或者說是規則。
2:獲得鎖,其實也就是獲得了訪問資源的權限。
一:Lock接口
上圖是Lock接口中的一些方法,下面說下每個方法的作用:
1:lock(),lock接口是對資源進行加鎖,而且加鎖的時候是不死不休的,就是我加不到鎖,我就一直等待着,直到我加到鎖爲止。
2:tryLock(),tryLock接口是嘗試加鎖,它就是我嘗試一下去加鎖,若是鎖已經被佔用,就不會再去等了。
3:tryLock(long time, TimeUnit unit),這個接口有個超時限制,若是鎖已經被佔用,就等待一段時間,時間到了後,要是鎖還是被佔用,就放棄。
4:lockInterruptibly(),lockInterruptibly是任人擺佈的,就是說當有別的線程調用了interrupt打斷它之後,它就不會再去加鎖了。
下面是一個測試例子:
//定義一個鎖
public static volatile Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
lock.lock();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("try lock start......");
lock.tryLock();
System.out.println("try lock end......");
System.out.println("lock start......");
lock.lock();
System.out.println("lock end......");
System.out.println("try lock start......");
try {
lock.tryLock(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("try lock end......");
System.out.println("try lock start......");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("try lock end......");
}
});
thread.start();
Thread.sleep(5000);
thread.interrupt();
Thread.sleep(2000);
lock.unlock();
}
二:Condition
之前說了wait、notify,掛起和喚醒線程,那麼Lock接口中也提供了一個Condition,Condition的底層實現機制是park和unpark,我們知道park和unpark當先喚醒後掛起時不會發生死鎖,那麼Condition也是一樣,而且Condition中的掛起也有釋放鎖的語義,所以Condition在加鎖或者先喚醒後掛起兩種情況下都不會死鎖,下面看下栗子:
//定義一個鎖
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("子線程獲取鎖。。。");
try {
System.out.println("子線程掛起開始。。。");
condition.await();
System.out.println("子線程掛起結束。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
thread.start();
Thread.sleep(2000);
lock.lock();
System.out.println("主線程獲取鎖。。。");
condition.signal();
lock.unlock();
}
public static void test2() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("子線程獲取鎖。。。");
try {
Thread.sleep(8000);
System.out.println("子線程掛起開始。。。");
condition.await();
System.out.println("子線程掛起結束。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
thread.start();
Thread.sleep(1000);
lock.lock();
System.out.println("主線程獲取鎖。。。");
condition.signal();
lock.unlock();
}
上面測試例子中,兩種情況都會執行完畢,不會死鎖。
wait/notify提供的等待集合是單個的,而Condition可以提供多個等待集,下面是測試例子:
public class ConditionDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadQueue queue = new ThreadQueue(5);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<20;i++){
try {
queue.put("元素" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
Thread.sleep(3000);
System.out.println("循環的從隊列拿元素。。。");
for (int i=0;i<10;i++){
queue.get();
Thread.sleep(3000);
}
}
}
//定義一個阻塞隊列
//put元素時,若隊列已滿,就阻塞,直到再有空間,未滿就put元素進去
//take元素時,若隊列中沒有元素,就阻塞,直到再有元素,有元素就直接取
class ThreadQueue {
//定義一個可重入鎖
Lock lock = new ReentrantLock();
Condition putCondition = lock.newCondition();
Condition getCondition = lock.newCondition();
//隊列長度
private volatile int length;
//用來存放元素的集合
List<Object> list = new ArrayList<>();
public ThreadQueue(int length) {
this.length = length;
}
//往隊列中放元素
public void put(Object obj) throws InterruptedException {
lock.lock();
for (;;) {
//若隊列還有空間,就直接放入隊列,並且喚醒拿元素的等待集合,可以去拿元素了
//若隊列空間已經滿了,就直接阻塞
if (list.size() < length) {
list.add(obj);
System.out.println("put: " + obj);
getCondition.signal();
break;
} else {
putCondition.await();
}
}
lock.unlock();
}
//從隊列中拿元素
public Object get() throws InterruptedException {
lock.lock();
Object obj;
for (;;) {
//若隊列中有元素就直接取,然後把取走後的元素從隊列中移除,並且喚醒放元素的等待集
合,可以繼續放元素了
//若隊列中沒有元素,就阻塞等待元素
if (list.size() > 0) {
obj = list.get(0);
list.remove(0);
System.out.println("get: " + obj);
putCondition.signal();
break;
} else {
getCondition.await();
}
}
lock.unlock();
return obj;
}
}
三:ReentrantLock
ReentrantLock是Lock接口的一個實現,它是一個可重入鎖,會有一個值去記錄重入的次數。若在一個線程中加鎖加了n次,那麼解鎖就要調用n次,如果加鎖次數大於解鎖次數,就不能完全釋放鎖,若加鎖次數小於解鎖次數,即解鎖多調了幾次,那麼就會報錯。下面是例子:
/定義一個鎖
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
System.out.println("here i am 1.......");
lock.lock();
System.out.println("here i am 2.......");
lock.lock();
System.out.println("here i am 3.......");
lock.unlock();
lock.unlock();
//會報java.lang.IllegalMonitorStateException異常
//lock.unlock();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("子線程獲取鎖.......");
lock.unlock();
}
}).start();
}
下面簡單實現一個可重入鎖:
思考:
1. 實現可重入鎖需要哪些準備呢?
2. 需要實現哪些方法呢?
那麼,實現一個可重入鎖,需要以下內容:
1:需要知道那個線程獲取到了鎖。
2:如果獲取不到鎖,就放入等待隊列,那麼就需要一個隊列存放掛起的線程。
3:需要知道線程重入的次數,即需要一個變量去記錄線程重入的次數。
4:需要加鎖,解鎖的方法
下面提供一個簡單的實現,有大量註釋,可以方便查閱:
public class ThreadLock2 implements Lock {
//用於顯示那個線程獲取到鎖
public volatile AtomicReference<Thread> owner = new AtomicReference<>();
//用於存放沒有獲取到鎖,掛起的線程
public static BlockingDeque<Thread> waiter = new LinkedBlockingDeque<>();
//鎖重入的次數
volatile AtomicInteger count = new AtomicInteger(0);
//嘗試加鎖
@Override
public boolean tryLock() {
int ct = count.get();
//ct!=0,說明鎖已被佔用
if (ct != 0) {
//判斷鎖的擁有者是不是當前線程,若是,就把重入次數加一
if (owner.get() == Thread.currentThread()) {
count.set(ct + 1);
return true;
}
//鎖未被佔用,就去搶鎖
} else {
//CAS方式去搶鎖
if (count.compareAndSet(ct, ct+1)) {
owner.set(Thread.currentThread());
return true;
}
}
return false;
}
//加鎖
@Override
public void lock() {
if (!tryLock()) {
//搶鎖失敗,就加入等待隊列
waiter.offer(Thread.currentThread());
//循環的去搶鎖
for (;;) {
//取出隊列頭部的線程
Thread head = waiter.peek();
//若隊列頭部的元素是當前線程,就去搶鎖
if (head == Thread.currentThread()) {
if (tryLock()) {
//搶到鎖就從等待隊列中取出
waiter.poll();
return;
} else {
//搶不到鎖就直接掛起
LockSupport.park();
}
} else {
//不是隊列頭部的線程,就直接掛起
LockSupport.park();
}
}
}
}
//解鎖
@Override
public void unlock() {
if (tryUnlock()) {
//釋放鎖成功後,就從等待隊列中喚醒線程
Thread thread = waiter.peek();
if (thread != null) {
LockSupport.unpark(thread);
}
}
}
//嘗試解鎖
public boolean tryUnlock() {
//判斷當前線程是不是鎖擁有者
//不是就拋出異常
//是的話就釋放鎖
if (owner.get() != Thread.currentThread()) {
//測試類運行可能會進入這個錯誤,這和環境可能有關,但是代碼的具體實現思路應該是沒有
//問題的
throw new IllegalMonitorStateException();
} else {
//釋放鎖,就把重入次數減一
int ct = count.get();
int nextCt = ct - 1;
count.set(nextCt);
//當重入次數爲0,就完全釋放鎖,把鎖擁有者置爲null
if (nextCt == 0) {
owner.set(null);
return true;
} else {
return false;
}
}
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
測試類:
static ThreadLock2 lock = new ThreadLock2();
static volatile int i = 0;
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<6;i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<100000;i++) {
add();
}
}
}).start();
}
Thread.sleep(2000);
System.out.println(i);
}
public static void add() {
lock.lock();
i++;
lock.unlock();
}
四:synchronized和Lock的區別
4.1 synchronized
優點:
1. synchronized使用起來比較簡單,語義也比較清晰。
2. JVM爲synchronized提供了很多的優化,如鎖消除,鎖粗化,偏向鎖等。
3. synchronized可以自動釋放鎖,可以避免死鎖發生
缺點:
無法實現鎖的一些高級特性,如公平鎖,共享鎖等。
4.2 Lock接口
優點:
synchronized的缺點就是Lock的缺點,Lock接口可以實現一些鎖的高級特性。
缺點:
Lock接口需要手動的去釋放鎖,使用中不注意的話可能會造成死鎖。
五:ReadWriteLock
ReadWriteLock讀寫鎖,提供了一個讀鎖,一個寫鎖,讀鎖是共享鎖,可以由多個線程持有,寫鎖是獨佔鎖,只能有一個線程可以獲得寫鎖,讀寫鎖適用於讀場景比較多的地方。
讀寫鎖,一個線程獲取到了讀鎖後就不能再去獲取寫鎖,讀寫是互斥的,一個線程獲取到了寫鎖,可以鎖降級爲讀鎖,降級爲讀鎖後,就獲取不了寫鎖了;寫鎖只能由一個線程獲取(寫寫互斥),讀鎖可以由多個線程共同獲取,想要獲取寫鎖,只能等到所有的讀鎖全部釋放後,纔可以去獲取,想要獲取讀鎖,也要等到寫鎖釋放後纔可以獲取。
獲取寫鎖的過程:
獲取讀鎖的過程:
下面是實現ReadWriteLock的一個例子:
public class ReadWriteLock2 {
//鎖擁有者
private Thread owner = null;
//等待隊列,存放阻塞掛起的線程
private LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
//讀鎖
private AtomicInteger readCount = new AtomicInteger(0);
//寫鎖
private AtomicInteger writeCount = new AtomicInteger(0);
class Node {
//標識是讀鎖還是寫鎖,0爲讀鎖,1爲寫鎖
int type = 0;
//線程
Thread thread = null;
int arg = 0;
public Node(int type, Thread thread, int arg) {
this.type = type;
this.thread = thread;
this.arg = arg;
}
}
//獲取寫鎖,即獨佔鎖
public void lock() {
int arg = 1;
//嘗試獲取獨佔鎖,若成功則退出,若失敗繼續搶鎖
if (!tryLock(arg)) {
//搶鎖失敗,就放入等待隊列
Node node = new Node(0, Thread.currentThread(), arg);
waiters.offer(node);
//自旋搶鎖
for(;;) {
Node head = waiters.peek();
//判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
if (head != null && head.thread == Thread.currentThread()) {
if (tryLock(arg)) {
//搶鎖成功就把線程從隊列中移除,然後返回
waiters.poll();
return;
} else {
//搶鎖失敗就掛起
LockSupport.park();
}
} else {
//不是隊列頭部就掛起
LockSupport.park();
}
}
}
}
//嘗試獲取寫鎖,即獨佔鎖
public boolean tryLock(int arg) {
//若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
if (readCount.get() != 0) {
return false;
}
int ct = writeCount.get();
if (ct != 0) {
//若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
if (owner == Thread.currentThread()) {
writeCount.set(ct + arg);
return true;
}
} else {
//若寫鎖沒被獲取,就用CAS方式去搶鎖
if (writeCount.compareAndSet(ct, ct + arg)){
//搶鎖成功就把當前線程賦予owner
owner = Thread.currentThread();
return true;
}
}
return false;
}
//釋放寫鎖,即獨佔鎖
public boolean unLock() {
int arg = 1;
if (tryUnLock(arg)) {
//釋放鎖成功,取出隊列頭部線程,喚醒
Node headNode = waiters.peek();
if (headNode != null) {
LockSupport.unpark(headNode.thread);
}
return true;
}
return false;
}
//嘗試釋放寫鎖,即獨佔鎖
public boolean tryUnLock(int arg) {
//若鎖的擁有者不是當前線程就拋異常
if (owner != Thread.currentThread()) {
throw new IllegalMonitorStateException();
} else {
//若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
int ct = writeCount.get();
int next = ct - arg;
if (next == 0) {
//把owner置爲null,釋放鎖成功
owner = null;
return true;
} else {
return false;
}
}
}
//獲取讀鎖,即共享鎖
public void sharedLock() {
int arg = 1;
if (trySharedLock(arg) < 0) {
//搶鎖失敗,就放入等待隊列
Node node = new Node(1, Thread.currentThread(), arg);
waiters.offer(node);
for (;;) {
Node headNode = waiters.peek();
//判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
if (headNode != null && headNode.thread == Thread.currentThread()) {
if (trySharedLock(arg) >= 0) {
//搶鎖成功就把線程從隊列中移除,然後返回
waiters.poll();
//若下一個線程是讀鎖,就把它喚醒
Node next = waiters.peek();
if (next != null && next.type == 0) {
LockSupport.unpark(next.thread);
}
} else {
//搶鎖失敗就掛起
LockSupport.park();
}
} else {
//不是隊列頭部就掛起
LockSupport.park();
}
}
}
}
//嘗試獲取讀鎖,即共享鎖
public int trySharedLock(int arg) {
//自旋搶鎖
for (;;) {
//若寫鎖沒有釋放,且不是當前線程持有,就返回錯誤值-1,因爲寫鎖釋放後,纔可以獲取讀鎖
if (writeCount.get() != 0 && owner != Thread.currentThread()) {
return -1;
}
int ct = readCount.get();
if (readCount.compareAndSet(ct, ct + arg)) {
return 1;
}
}
}
//嘗試釋放讀鎖,即共享鎖
public boolean tryUnSharedLock(int arg) {
for (;;) {
int ct = readCount.get();
int nextCt = ct - arg;
//直到readCount爲0時纔是完全釋放鎖
if (readCount.compareAndSet(ct, nextCt)) {
return nextCt == 0;
}
}
}
//釋放讀鎖,即共享鎖
public boolean unSharedLock() {
int arg = 1;
if (tryUnSharedLock(arg)) {
//釋放鎖成功,取出隊列頭部線程,喚醒
Node headNode = waiters.peek();
if (headNode != null) {
LockSupport.unpark(headNode.thread);
}
return true;
}
return false;
}
}
測試類:
static ReadWriteLock2 lock = new ReadWriteLock2();
volatile static int i = 0;
static void add() {
i++;
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
for (int a=1; a<=20000; a++){
final int n = a;
new Thread(new Runnable() {
@Override
public void run() {
if (n%5 ==0){
lock.lock();
add();
lock.unLock();
}else{
lock.sharedLock();
System.out.println("i=" +i);
int a = i;
lock.unSharedLock();
}
}
}).start();
}
while (true){
System.out.println("目前耗時:" + (System.currentTimeMillis()-startTime) /1000 + "s");
Thread.sleep(1000L);
System.out.println("i=" + i);
}
}
下面提供另外兩個例子:改造hashMap爲安全的,和實現模擬一個緩存:
public class MapDemo {
//將hashMap改造成線程安全的
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
//初始化讀鎖和寫鎖
private Lock writeLock = lock.writeLock();
private Lock readLock = lock.readLock();
//根據key值獲取元素
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
//獲取所有的key
public Object[] allKeys() {
readLock.lock();
try {
return map.keySet().toArray();
} finally {
readLock.unlock();
}
}
//放入元素
public Object put(String key, String value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
//清除所有元素
public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
}
//做一個緩存,從緩存中取元素
public class CacheDemo {
//初始化一個讀寫鎖
private static ReadWriteLock lock = new ReentrantReadWriteLock();
//用於判斷緩存是否可用,是否有元素
private static volatile boolean isCache;
static Object get(String key) {
Object obj = null;
//加讀鎖
lock.readLock().lock();
try {
//若緩存可用,直接從緩存中取數據
if(isCache) {
obj = Cache.map.get(key);
} else {
//釋放讀鎖
lock.readLock().unlock();
//加寫鎖,並不會馬上獲取到寫鎖,會等到所有讀鎖釋放
lock.writeLock().lock();
try {
//若緩存不可用,從數據庫取數據,然後放入緩存
if (!isCache) {
obj = dataBaseGetData.getData();
Cache.map.put(key, obj);
isCache = true;
}
//鎖降級,將寫鎖降級爲讀鎖
lock.readLock().lock();
} finally {
//釋放寫鎖
lock.writeLock().unlock();
}
}
} finally {
//釋放讀鎖
lock.readLock().unlock();
}
return obj;
}
}
//模擬從數據庫獲取元素
class dataBaseGetData {
static String getData() {
System.out.println("從數據庫取元素。。。");
return "name:hello,age:20";
}
}
//模擬緩存
class Cache {
static Map<String, Object> map = new HashMap<>();
}
六:模板模式
模板模式就是說,有一個母版,就像PPT一樣,然後把母版複製過來自己去實現自己想要的就可以了,下面是個簡單的例子:
//母版
class mb {
void title() {
throw new UnsupportedOperationException();
}
void content() {
throw new UnsupportedOperationException();
}
void foot() {
throw new UnsupportedOperationException();
}
public final void show() {
System.out.println("這是一個標題");
title();
System.out.println("字體:微軟雅黑");
System.out.println("這是內容:");
content();
System.out.println("內容結束");
System.out.println("這是底部:");
foot();
}
}
class PPT1 extends mb {
@Override
void title() {
System.out.print("你好啊");
}
@Override
void content() {
System.out.print("java。。。");
System.out.print("c++");
System.out.print("中國");
}
@Override
void foot() {
System.out.print("結束了");
}
}
根據上面的例子改造ReentrantLock和ReadWriteLock的實現方式,先做一個模板:
public class CommonLock {
//鎖擁有者
Thread owner = null;
//等待隊列,存放阻塞掛起的線程
LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
//讀鎖
AtomicInteger readCount = new AtomicInteger(0);
//寫鎖
AtomicInteger writeCount = new AtomicInteger(0);
class Node {
//標識是讀鎖還是寫鎖,0爲讀鎖,1爲寫鎖
int type = 0;
//線程
Thread thread = null;
int arg = 0;
public Node(int type, Thread thread, int arg) {
this.type = type;
this.thread = thread;
this.arg = arg;
}
}
//獲取讀鎖,即獨佔鎖
public void lock() {
int arg = 1;
//嘗試獲取獨佔鎖,若成功則退出,若失敗繼續搶鎖
if (!tryLock(arg)) {
//搶鎖失敗,就放入等待隊列
Node node = new Node(0, Thread.currentThread(), arg);
waiters.offer(node);
//自旋搶鎖
for(;;) {
Node head = waiters.peek();
//判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
if (head != null && head.thread == Thread.currentThread()) {
if (tryLock(arg)) {
//搶鎖成功就把線程從隊列中移除,然後返回
waiters.poll();
return;
} else {
//搶鎖失敗就掛起
LockSupport.park();
}
} else {
//不是隊列頭部就掛起
LockSupport.park();
}
}
}
}
//釋放寫鎖,即獨佔鎖
public boolean unLock() {
int arg = 1;
if (tryUnLock(arg)) {
//釋放鎖成功,取出隊列頭部線程,喚醒
Node headNode = waiters.peek();
if (headNode != null) {
LockSupport.unpark(headNode.thread);
}
return true;
}
return false;
}
//獲取讀鎖,即共享鎖
public void sharedLock() {
int arg = 1;
if (trySharedLock(arg) < 0) {
//搶鎖失敗,就放入等待隊列
Node node = new Node(1, Thread.currentThread(), arg);
waiters.offer(node);
for (;;) {
Node headNode = waiters.peek();
//判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
if (headNode != null && headNode.thread == Thread.currentThread()) {
if (trySharedLock(arg) >= 0) {
//搶鎖成功就把線程從隊列中移除,然後返回
waiters.poll();
//若下一個線程是讀鎖,就把它喚醒
Node next = waiters.peek();
if (next != null && next.type == 1) {
LockSupport.unpark(next.thread);
}
} else {
//搶鎖失敗就掛起
LockSupport.park();
}
} else {
//不是隊列頭部就掛起
LockSupport.park();
}
}
}
}
//釋放讀鎖,即共享鎖
public boolean unSharedLock() {
int arg = 1;
if (tryUnSharedLock(arg)) {
//釋放鎖成功,取出隊列頭部線程,喚醒
Node headNode = waiters.peek();
if (headNode != null) {
LockSupport.unpark(headNode.thread);
}
return true;
}
return false;
}
//嘗試獲取寫鎖,即獨佔鎖
public boolean tryLock(int arg) {
throw new UnsupportedOperationException();
}
//嘗試釋放寫鎖,即獨佔鎖
public boolean tryUnLock(int arg) {
throw new UnsupportedOperationException();
}
//嘗試獲取讀鎖,即共享鎖
public int trySharedLock(int arg) {
throw new UnsupportedOperationException();
}
//嘗試釋放讀鎖,即共享鎖
public boolean tryUnSharedLock(int arg) {
throw new UnsupportedOperationException();
}
}
改造後的ReentrantLock:
//private Thread owner = null;
//鎖擁有者
volatile AtomicReference<Thread> owner = new AtomicReference<>();
//等待隊列
private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
//記錄重入的次數
volatile AtomicInteger count = new AtomicInteger(0);
CommonLock lock = new CommonLock(){
//嘗試獲取寫鎖,即獨佔鎖
@Override
public boolean tryLock(int arg) {
//若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
if (lock.readCount.get() != 0) {
return false;
}
int ct = lock.writeCount.get();
if (ct != 0) {
//若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
if (lock.owner == Thread.currentThread()) {
lock.writeCount.set(ct + arg);
return true;
}
} else {
//若寫鎖沒被獲取,就用CAS方式去搶鎖
if (lock.writeCount.compareAndSet(ct, ct + arg)){
//搶鎖成功就把當前線程賦予owner
lock.owner = Thread.currentThread();
return true;
}
}
return false;
}
//嘗試釋放寫鎖,即獨佔鎖
@Override
public boolean tryUnLock(int arg) {
//若鎖的擁有者不是當前線程就拋異常
if (lock.owner != Thread.currentThread()) {
throw new IllegalMonitorStateException();
} else {
//若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
int ct = lock.writeCount.get();
int next = ct - arg;
if (next == 0) {
//把owner置爲null,釋放鎖成功
lock.owner = null;
return true;
} else {
return false;
}
}
}
};
@Override
public void lock() {
lock.lock();
}
@Override
public void unlock() {
lock.unLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return lock.tryLock(1);
}
public boolean tryUnlock() {
return lock.tryUnLock(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
改造後的ReadWriteLock:
public class ReadWriteLock2 implements ReadWriteLock {
CommonLock lock = new CommonLock(){
//嘗試獲取寫鎖,即獨佔鎖
@Override
public boolean tryLock(int arg) {
//若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
if (lock.readCount.get() != 0) {
return false;
}
int ct = lock.writeCount.get();
if (ct != 0) {
//若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
if (lock.owner == Thread.currentThread()) {
lock.writeCount.set(ct + arg);
return true;
}
} else {
//若寫鎖沒被獲取,就用CAS方式去搶鎖
if (lock.writeCount.compareAndSet(ct, ct + arg)){
//搶鎖成功就把當前線程賦予owner
lock.owner = Thread.currentThread();
return true;
}
}
return false;
}
//嘗試釋放寫鎖,即獨佔鎖
@Override
public boolean tryUnLock(int arg) {
//若鎖的擁有者不是當前線程就拋異常
if (lock.owner != Thread.currentThread()) {
throw new IllegalMonitorStateException();
} else {
//若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
int ct = lock.writeCount.get();
int next = ct - arg;
if (next == 0) {
//把owner置爲null,釋放鎖成功
lock.owner = null;
return true;
} else {
return false;
}
}
}
//嘗試獲取讀鎖,即共享鎖
@Override
public int trySharedLock(int arg) {
//自旋搶鎖
for (;;) {
//若寫鎖沒有釋放,且不是當前線程持有,就返回錯誤值-1,因爲寫鎖釋放後,纔可以獲取讀鎖
if (writeCount.get() != 0 && owner != Thread.currentThread()) {
return -1;
}
int ct = readCount.get();
if (readCount.compareAndSet(ct, ct + arg)) {
return 1;
}
}
}
//嘗試釋放讀鎖,即共享鎖
@Override
public boolean tryUnSharedLock(int arg) {
for (;;) {
int ct = readCount.get();
int nextCt = ct - arg;
//直到readCount爲0時纔是完全釋放鎖
if (readCount.compareAndSet(ct, nextCt)) {
return nextCt == 0;
}
}
}
};
@Override
public Lock readLock() {
return new Lock() {
@Override
public void lock() {
lock.sharedLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return lock.trySharedLock(0) == 1;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
lock.tryUnSharedLock(0);
}
@Override
public Condition newCondition() {
return null;
}
};
}
@Override
public Lock writeLock() {
return new Lock() {
@Override
public void lock() {
lock.lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return lock.tryLock(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
lock.unLock();
}
@Override
public Condition newCondition() {
return null;
}
};
}
}
七:AQS抽象隊列同步器
JUC包就是基於AQS實現的,AQS的設計模式就是模板模式,數據結構是雙向鏈表和鎖狀態(state),底層實現是CAS。它的state不像上面讀寫鎖說的是兩個count,它用一個state實現,即我們知道int類型有8個字節,它的實現是前4個字節可以標識讀鎖,後四個字節可以標識寫鎖。
AQS本文不做詳細解釋。。。
好了到此,Lock接口的相關內容已經結束了。