AQS
概念
AbstractQueueSyncronizer 同步發生器 用於構建LOCK JUC下面的ReentrantLock就是基於AQS實現的,實際上JUC下面的很多類都是通過AQS實現的
基本思想
通過內置的FIFO同步隊列來完成線程爭奪資源的管理工作
CLH同步隊列
每一個線程就是一個Node,有兩個指針,一個指向前驅,一個指向後繼.
最前端是一個同步器,是一個傀儡節點,兩個指針分別指向head頭和tail尾
競爭資源用一個同步的狀態去描述 (volatile int state)
如果state等於0表示沒有線程去佔用它,大於等於1時有線程佔用它
當一個線程來了之後首先會去判斷state的值,如果爲0再去拿,否則進入等待隊列中
每個線程要做的事:
- 通過自旋獲取鎖(tryAcquire)
- 釋放鎖
四個waitState
- CANCELED = 1 取消(因中斷或者完成退出隊列)
- SIGNAL = -1 信號(節點的繼任節點被阻塞)
- CONDITION = -2 條件(條件阻塞)
- PROPAGATE = -3 等待狀態傳播(共享模式下頭結點的狀態)
自定義鎖
一. 鎖
AQS寫一個鎖
子類定義爲非公共內部幫助類(私有內部類繼承AQS),寫鎖的時候的一個幫助器,提供獲取鎖和釋放鎖的功能.
–>模板
acquire() 以獨佔模式獲取對象,忽略中斷
acquireShared() 以共享模式獲取對象,忽略中斷
tryAcquire() 試圖在獨佔模式下獲取對象狀態
tryAcquireShared() 試圖在共享模式下獲取對象狀態
release() 以獨佔方式釋放對象
releaseShared() 以共享方式釋放對象
可重入性:同一個鎖多統一資源進行佔有的時候,直接分配給這個線程.
案例:
package AQS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
private Helper helper = new Helper();
private class Helper extends AbstractQueuedSynchronizer {
//獲取鎖
@Override
protected boolean tryAcquire(int arg) {
if (getState() == 0) {
//利用CAS原理修改state
if (compareAndSetState(0, arg)) {
//設置當前線程佔有資源
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}else if (getExclusiveOwnerThread()==Thread.currentThread()){
//可重入性判斷,如果申請鎖的線程是當前線程,將state加一返回true
setState(getState()+arg);
return true;
}
return false;
}
//釋放鎖
@Override
protected boolean tryRelease(int arg) {
int state = getState() - arg;
//判斷釋放後是否爲0
if (state == 0) {
setExclusiveOwnerThread(null);
setState(state); //此時爲0
return true;
}
//此時是線程安全的,因爲要"釋放"鎖的前提是已經"拿到"了鎖
setState(state); //可重入鎖的體現,此時爲state-arg
return false;
}
public Condition newConditionObject() {
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
helper.release(1);
}
@Override
public Condition newCondition() {
return helper.newConditionObject();
}
}
package AQS;
import java.util.concurrent.TimeUnit;
public class Demo {
private MyLock lock = new MyLock();
private int m = 0;
public int next(){
lock.lock();
try {
return m++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
for (int i = 0; i < 20; i++) {
new Thread(()->{
System.out.println(demo.next());
}).start();
}
}
}
ReentrantLock
基於AQS實現
公平鎖
非公平鎖
ReentrantReadWriteLock
讀取者優先或寫入者優先強加給鎖訪問的排序.但是,它確實支持可選的公平策略.
讀操作加readLock,寫操作加writeLock.
在write.unlock()之前先加read.lock(),鎖降級,寫鎖降級到讀鎖
二. 併發工具
CountdownLatch
CountDownl atch類位於java util.concurrent包下,利用它可以實現類似計數器的功能。
比如有一個任務A,它要等待其他4個任務執行完畢之後才能執行,可以利用CountDownLatch來實現.
CountDownLatch是通過一個計數器來實現的, 計數器的初始值爲線程的數量。
每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。
CountdownLatch如何工作?
構造一個用給定計數初始化的CountdownLatch(int count)
構造器中的計數值(count)實際上就是團鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownlatch沒有提供任何機制去重新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啓動其他線程後立即調用CountDownlatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
其他N個線程必須引用閉鎖對象,因爲他們需要通知CountDownLatch對象他們已經完成了各自的任務。這種通知機制是通過CountDownLatch.countDown()方法來完成的;
每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。
案例:
package AQS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownDemo {
private static List<String> company = Arrays.asList("東方航空","南方航空","海南航空");
private static List<String> flightLIst = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
String origin = "Beijing";
String destination = "Shanghai";
CountDownLatch latch = new CountDownLatch(company.size());
for (int i = 0; i < company.size(); i++) {
String name = company.get(i);
new Thread(()->{
System.out.println(name+"查詢從"+origin+"到"+destination+"的機票");
int val = new Random().nextInt(10);//隨機產生票數
try {
TimeUnit.SECONDS.sleep(val);
flightLIst.add(name+"--"+val);
System.out.println(name+"查詢成功");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
latch.await();
System.out.println("======查詢結果======");
flightLIst.forEach(str->System.out.println(str));
}
}
結果如下:(3個線程查詢機票,主線程返回查詢的結果list)
CyclicBarrier
需要所有的子任務都完成時,才執行主任務,這個時候就可以選擇使用CyclicBarrier.
基本原理:
每個線程執行時,都會碰到一個屏障,直到所有線程執行結束,然後屏障便會打開,使所有線程繼續往下執行。
案例:
package AQS;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(8);
for (int i = 0; i < 8; i++) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
System.out.println(Thread.currentThread().getName()+"準備好了");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"起跑");
},"運動員"+i).start();
}
}
}
運行結果:
在CyclicBarrier的內部定義了一個Lock對象,每當-個線程調用await方法時,將攔截的線程數加1,然後判斷剩餘攔截數是否爲初始值parties,如果不是,進入Lock對象的條件隊列等待。如果是,執行barrierAction對象的Runnable方法,然後將鎖的條件隊列中的所有線程放入鎖等待隊列中,這些線程會依次的獲取鎖、釋放鎖.
CyclicBarrier的兩個構造函數: CyclicBarrier(int parties)和CyclicBarrier(int parties,Runnable barrierAction) :前者只需要聲明需要攔截的線程數即可,而後者還需要定義一個等待所有線程到達屏障優先執行的Runnable對象。
一般情況下對於兩個非常相似的類,我們一-般都會想當然地去把他們進行類比。對於CountDownLatch 和CyclicBarrier 兩個類,我們可以看到CountDownLatch 類都是一個類似於集結點的概念,很多個線程做完事情之後等待其他線程完成,全部線程完成之後再恢復運行。不同的是CountDownLatch 類需要你自己調用countDown() 方法減少一個計數,然後調用await() 方法即可。而CyclicBarrier 則直接調用await() 方法即可。所以從上面來看,CountDownLatch 更傾向於多個線程合作的情況,等你所有東西都準備好了,我這邊就自動執行了。CyclicBarrier 則是我們都在一 個地方等你,大家到齊了,大家再一起執行。
Semaphore
熟悉的操作系統信號量PV機制