1. 程式人生 > >java高併發總結-常用於面試複習

java高併發總結-常用於面試複習

定義:
獨佔鎖是一種悲觀保守的加鎖策略,它避免了讀/讀衝突,如果某個只讀執行緒獲取鎖,則其他讀執行緒都只能等待,這種情況下就限制了不必要的併發性,因為讀操作並不會影響資料的一致性。
共享鎖則是一種樂觀鎖,它放寬了加鎖策略,允許多個執行讀操作的執行緒同時訪問共享資源。
分類:
獨佔鎖: ReentrantLock, ReentrantReadWriteLock.WriteLock
共享鎖:ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享鎖
其他:
wait(), notify() @hxx,對應Contition的 await 和 signal
前者是object,在程式碼中用鎖物件即可呼叫,後者是一個即可,必須使用ReentrantLock生成
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
Synchronized
ReentrantLock是Synchronized一種更高階的改進
1: 更加面向物件,效能更好
2: 提供了更多機制,比如時間鎖等候,可中斷鎖等候,鎖投票等
3: ReentrantLock提供了可輪詢的鎖請求,提供tryLock()方法;而synchronized則會一直阻塞等待
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// 資源正在被別人寫,先做別的事情吧,比如說列印一行日誌
}
注意事項:
除了Synchronized自動走出鎖範圍,其餘佔有了鎖,一定要記得釋放鎖,可以在finally中釋放!!!!
公平引數,預設都是關閉的,所以不要以為等的時間越長就能更大機率獲得鎖!公平和非公平對應著:公平鎖和非公平鎖(Sync類的兩個子類),非公平鎖無視等待佇列,直接上來就是搶!
java實現鎖的底層都是使用Sync類,private final Sync sync;  
是繼承了AbstractQueuedSynchronizer的內部抽象類,主要由它負責實現鎖的功能。關於 AbstractQueuedSynchronizer 只要知道它內部存在一個獲取鎖的等待佇列及其互斥鎖狀態下的int狀態位(0當前沒有執行緒持有該鎖、n存在某執行緒重入鎖n次)即可,該狀態位也可用於其它諸如共享鎖、訊號量等功能。
----------------------------------------
Demo code:
1: CountDownLatch
用於n個執行緒等待其餘M個執行緒結束, 類位於java.util.concurrent包下
程式碼:
CountDownLatch doneSignal = new CountDownLatch(3); // 定義了計數器為3.表示有3個執行緒結束即可!
for(int i=0; i<5; i++)
new InnerThread().start(); // m個執行緒,InnerThread的run方法末尾中寫了doneSignal .countDown(); 必須手動減
doneSignal.await(); // 阻塞,直到3個執行緒結束
//  await(long timeout, TimeUnit unit) throws InterruptedException { }; 這個就可以實現超時功能
2: CyclicBarrier
java.util.concurrent包,實現 M 個執行緒在barrier柵欄處互相等待,可以重用狀態(所以叫cycli),1的計數器只能減不能重新賦值!
程式碼:
CyclicBarrier barrier = new CyclicBarrier(N);// N個執行緒互相等
for(int i=0;i<N;i++)
new Writer(barrier).start(); // Writer的run方法需要M個執行緒一起完成的點中寫了barrier.await();

3: Semaphore
java.util.concurrent包,用於限制最多M個執行緒同時併發
程式碼:
int N = 8; //工人數
Semaphore semaphore = new Semaphore(5); //機器數目, 限制了最多5個併發, 預設是不公平的構造可以加true
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
@Override
public void run() {
try {
semaphore.acquire(); // 拿憑證,總共5張憑證,沒拿到會阻塞, 不想阻塞可用tryAcquire
System.out.println("工人"+this.num+"佔用一個機器在生產...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"釋放出機器");
semaphore.release(); // 釋放憑證
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4: ReentrantLock
java.util.concurrent包,用於實現同步功能,與synchronized相似但是面向物件更靈活
ReentrantLock takeLock = new ReentrantLock();
// 獲取鎖
takeLock.lock();
try {
// 業務邏輯
} finally {
// 釋放鎖
takeLock.unlock();
}
5: ReentrantReadWriteLock
4是其父類,根據名字可以看到這個類區分了讀寫鎖,與4的優勢是執行緒大多在資料結構中讀取資料而少有修改時這個就有用了。
讀寫鎖要實現的功能是三點: 多個讀互相不干擾;多個寫操作必須獨佔鎖;讀和寫操作要上鎖!(@hxx第一個分號我我為什麼要用黑色,因為這就是讀寫鎖出現的意義,提升了讀的效能,不然就用4了)
如何實現的呢? 看先程式碼!
以下這段程式碼來自jdk文件中demo,寫的非常好!!
class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); //step1 @hxx 任何人都可以讀資料,因為沒人寫,提高效能 if (!cacheValid) { // @hxx 如果有快取了,就不用去load資料庫寫快取了,相當於if(cache.get(key) == null) // Must release read lock before acquiring write lock rwl.readLock().unlock(); // @hxx 為什麼 拿“寫鎖” 前要釋放掉 “讀鎖”?? 要判斷沒有人拿 “讀鎖” 才可以寫嗎,是這樣實現讀寫互斥的嗎? 如果是,那麼我在這裡不釋放,是不是就違背了機制,try後,確實是的 rwl.writeLock().lock();
// @hxx 從這裡開始,保證一次只有一個執行緒進來啦 begin!! // Recheck state because another thread might have acquired // write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock
// @hxx,我先那個讀鎖,哈哈, 在我釋放讀鎖之前,誰也別想拿寫鎖,因為寫鎖拿到的前提是沒有人拿讀鎖 rwl.readLock().lock(); rwl.writeLock().unlock(); // Unlock write, still hold read
// @hxx 從這裡開始,保證一次只有一個執行緒進來啦 end!! } use(data); // step2 @hxx 我在讀資料不怕別人寫,真的爽! rwl.readLock().unlock(); // @hxx 現在我釋放了讀鎖,去拿寫鎖吧! } }
ps: 上面程式碼。快取一旦初始化成功後,後續的邏輯就只會走step1 + step2 了,就只有讀鎖共享了!
6: ReentrantReadWriteLock.ReadLock 和ReentrantReadWriteLock.WriteLock

看jdk文件裡面。以為又是一個新的東西,其實就是5中程式碼
ReentrantReadWriteLock.writeLock() 返回的物件
ReentrantReadWriteLock.readLock() 返回的物件
略過
7: wait(), notify()
java.lang.Object 的方法
檢視該筆記https://note.youdao.com/web/#/file/WEB82c69340fd16ecd931ee5920c6dc88cf/note/WEB384aff600e287fc0656f08883ad894d4/
8: Condition介面 (需要與ReentrantLock配合使用)
條件變數很大一個程度上是為了解決Object.wait/notify/notifyAll難以使用的問題,@hxx也就是消費者生產者程式碼中判斷佇列是否滿,是否空的條件!
程式碼如下:
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();

private Queue<Integer> buffer = new LinkedList<>();
int max = 100;
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();// 先拿鎖
try {
while (max == buffer.size())
notFull.await();
// 不滿,則可以放
buffer.add(1);
// 提醒可以取
notEmpty.signal();
} finally {// 記得釋放鎖
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
//不空, 可以取
int x= buffer.poll();
// 提醒可放
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
8: synchronized