二刷Java多線程:Java併發包中線程同步器詳解(CountDownLatch、CyclicBarrier、Semaphore)

一、等待多線程完成的CountDownLatch

1、案例介紹

public class CountDownLatchDemo {
    private static CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("child threadOne over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        });

        executorService.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("child threadTwo over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        });
        System.out.println("wait all child thread over");
        countDownLatch.await();
        System.out.println("all child thread over");
        executorService.shutdown();
    }
}

創建了一個CountDownLatch實例,因爲有兩個子線程所以構造函數的傳參爲2。主線程調用countDownLatch.await()方法後會被阻塞。子線程執行完畢後調用countDownLatch.countDown()方法讓countDownLatch內部的計數器減1,所有子線程執行完畢並調用countDown()方法後計數器會變爲0,這時候主線程的await()方法纔會返回

2、CountDownLatch源碼分析

在這裏插入圖片描述
CountDownLatch是使用AQS實現的,通過下面的構造函數把計數器的值賦給了AQS的狀態變量state,也就是使用AQS的狀態值來表示計數器值

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
        Sync(int count) {
            setState(count);
        }

1)、await()方法

當線程調用CountDownLatch對象的await()方法後,當前線程會被阻塞,直到下面的情況之一發生纔會返回:當所有線程都調用了CountDownLatch對象的countDown()方法後,也就是計數器的值爲0時;其他線程調用了當前線程的interrupt()方法中斷了當前線程,當前線程就會拋出InterruptedException異常,然後返回

    public void await() throws InterruptedException {
        //調用AQS中的模板方法acquireSharedInterruptibly
        sync.acquireSharedInterruptibly(1);
    }

AQS中的acquireSharedInterruptibly方法:

    //AQS獲取共享資源時可被中斷的方法
	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //如果線程被中斷則拋出異常
        if (Thread.interrupted())
            throw new InterruptedException();
        //調用CountDownLatch中sync重寫的tryAcquireShared方法,查看當前計數器值是否爲0,爲0則直接返回,否則進入AQS的隊列等待
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

sync類實現的AQS的接口:

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

2)、countDown()方法

    public void countDown() {
        //調用AQS中的模板方法releaseShared
        sync.releaseShared(1);
    }

AQS中的releaseShared方法:

    public final boolean releaseShared(int arg) {
        //調用CountDownLatch中sync重寫的tryReleaseShared方法
        if (tryReleaseShared(arg)) {
            //AQS的釋放資源方法
            doReleaseShared();
            return true;
        }
        return false;
    }

sync類實現的AQS的接口:

        protected boolean tryReleaseShared(int releases) {
            //循環進行CAS,直到當前線程成功完成CAS使計數器值(狀態值state)減1並更新到state
            for (;;) {
                int c = getState();
                //如果當前狀態值爲0則直接返回(爲了防止當計數器值爲0後,其他線程又調用了countDown方法,防止狀態值變爲負數)
                if (c == 0)
                    return false;
                //使用CAS讓計數器值減1
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

二、同步屏障CyclicBarrier

1、案例介紹

public class CyclicBarrierDemo {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "執行回調方法");
        }
    });

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " step1");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step2");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " step1");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step2");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + " step3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        executorService.shutdown();
    }
}

運行結果:

pool-1-thread-1 step1
pool-1-thread-2 step1
pool-1-thread-2執行回調方法
pool-1-thread-1 step2
pool-1-thread-2 step2
pool-1-thread-2執行回調方法
pool-1-thread-1 step3
pool-1-thread-2 step3

多個線程之間是相互等待的,假如計數器值爲N,那麼隨後調用await()方法的N-1個線程都會因爲到達屏障點而被阻塞,當第N個線程調用await()後,計數器值爲0了,這時候第N個線程纔會發出通知喚醒前面的N-1個線程。也就是當全部線程都到達屏障點時才能一塊繼續向下執行

此外從上面的案例中還可以得知,CyclicBarrier的計數器具備自動重置的功能,可以循環利用,回調任務是由最後一個到達屏障的線程執行的

2、CyclicBarrier源碼分析

在這裏插入圖片描述
CyclicBarrier基於獨佔鎖實現,本質底層還是基於AQS的。parties用來記錄線程個數,這裏表示多少線程調用await()後,所有線程纔會衝破屏障繼續往下運行。而count—開始等於parties,每當有線程調用await()方法就遞減1,當count爲0時就表示所有線程都到了屏障點。而parties始終用來記錄總的線程個數,當count計數器值變爲0後,會將parties的值賦給count,從而進行復用

   public CyclicBarrier(int parties, Runnable barrierAction) {
       if (parties <= 0) throw new IllegalArgumentException();
       this.parties = parties;
       this.count = parties;
       this.barrierCommand = barrierAction;
   }

還有一個變量barrierCommand也通過構造函數傳遞,這是一個任務,這個任務的執行時機是當所有線程都到達屏障點後。使用lock首先保證了更新計數器count的原子性,另外使用lock的條件變量trip支持線程間使用await()和signal()操作進行同步

在變量generation內部有一個變量broken,其用來記錄當前屏障是否被打破。這裏的broken並沒有被聲明爲volatile的,因爲是在鎖內使用變量,所以不需要聲明

    private static class Generation {
        boolean broken = false;
    }

await()方法:

當前線程調用CyclicBarrier的該方法時會被阻塞,直到滿足下麪條件之一纔會返回:parties個線程都調用了await()方法,也就是線程都到了屏障點;其他線程調用了當前線程的interrupt()方法中斷了當前線程,則當前線程會拋出InterruptedException異常而返回;與當前屏障點關聯的Generation對象的broken標誌被設置爲true時,會拋出BrokenBarrierException異常,然後返回

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            //調用內部的dowait()方法,第一個參數爲flase表示不設置超時時間
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

dowait()方法:

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            //如果index==0則說明所有線程都到了屏障點,此時執行初始化時傳遞的任務
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //(1)執行任務
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //(2)激活其他因調用await方法而被阻塞的線程,並重置CyclicBarrier
                    nextGeneration();
                    //返回
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            //(3)index!=0
            for (;;) {
                try {
                    //沒有設置超時時間
                    if (!timed)
                        trip.await();
                    //設置了超時時間
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

nextGeneration()方法:

    private void nextGeneration() {
        //喚醒條件隊列裏面阻塞的線程
        trip.signalAll();
        //重置CyclicBarrier
        count = parties;
        generation = new Generation();
    }

當一個線程調用了dowait方法後,首先會獲取獨佔鎖lock,如果創建CycleBarrier時傳遞的參數爲10,那麼後面9個調用線程會被阻塞。然後當前獲取到鎖的線程會對計數器count進行遞減操作,遞減後count=index=9,因爲index!=0所以當前線程會執行代碼(3)。如果當前線程調用的是無參數的await()方法,則這裏timed=false,所以當前線程會被放入條件變量trip的條件阻塞隊列,當前線程會被掛起並釋放獲取的lock鎖。如果調用的是有參數的await方法則timed=true,然後當前線程也會被放入條件變量的條件隊列並釋放鎖資源,不同的是當前線程會在指定時間超時後自動被激活

當第一個獲取鎖的線程由於被阻塞釋放鎖後,被阻塞的9個線程中有一個會競爭到lock鎖,然後執行與第一個線程同樣的操作,直到最後一個線程獲取到lock鎖,此時已經有9個線程被放入了條件變量trip的條件隊列裏面。最後count=index等於0,所以執行代碼(1),如果創建CyclicBarrier時傳遞了任務,則在其他線程被喚醒前先執行任務,任務執行完畢後再執行代碼(2),喚醒其他9個線程,並重置CyclicBarrier,然後這10個線程就可以繼續向下運行了

三、使用CountDownLatchDown和CyclicBarrier優化對賬系統

在學習極客時間的《Java併發編程實戰》這門課時,關於CountDownLatchDown和CyclicBarrier的文章《CountDownLatch和CyclicBarrier:如何讓多線程步調一致?》中,王寶令老師通過一個優化對賬系統的案例更好的詮釋了CountDownLatchDown和CyclicBarrier的使用,同時作者解決問題的思路也值得我們借鑑,下面是我對文章進行的總結和梳理,同時也強烈推薦學習一下這門課程,對併發編程的講解還是很不錯的

對賬系統業務:用戶通過在線商城下單,會生成電子訂單,保存在訂單庫;之後物流會生成派送單給用戶發貨,派送單保存在派送單庫。爲了防止漏派送或者重複派送,對賬系統每天還會校驗是否存在異常訂單
在這裏插入圖片描述
對賬系統的核心代碼如下,就是在一個單線程裏面循環查詢訂單、派送單,然後執行對賬,最後將寫入差異庫

		while (存在未對賬訂單) {
			//查詢未對賬訂單
			pos = getPOrders();
			//查詢派送單
			dos = getDOrders();
			//執行對賬操作
			diff = check(pos, dos);
			//差異寫入差異庫
			save(diff);
		}

首先要優化性能,就要找到這個對賬系統的瓶頸所在:目前的對賬系統,由於訂單量和派送單量巨大,所以查詢未對賬訂單getPOrders()和查詢派送單getDOrders()相對較慢,目前對賬系統是單線程執行的,圖形化後是下圖這個樣子
在這裏插入圖片描述
查詢未對賬訂單getPOrders()和查詢派送單getDOrders()這兩個操作並沒有先後順序的依賴可以並行處理,執行過程如下圖所示。對比一下單線程的執行示意圖,在同等時間內,並行執行的吞吐量近乎單線程的2倍
在這裏插入圖片描述

1、用CountDownLatch實現線程等待

		//創建2個線程的線程池
		Executor executor = Executors.newFixedThreadPool(2);
		while (存在未對賬訂單) {
			//計數器初始化爲2
			CountDownLatch latch = new CountDownLatch(2);
			//查詢未對賬訂單
			executor.execute(() -> {
				pos = getPOrders();
				latch.countDown();
			});
			//查詢派送單
			executor.execute(() -> {
				dos = getDOrders();
				latch.countDown();
			});

			//等待兩個查詢操作結束
			latch.await();

			//執行對賬操作
			diff = check(pos, dos);
			//差異寫入差異庫
			save(diff);
		}

在while循環裏面,創建了一個CountDownLatch,計數器的初始值等於2,之後在pos = getPOrders();和dos = getDOrders();兩條語句的後面對計數器執行減1操作,這個對計數器減1的操作是通過調用latch.countDown();來實現的。在主線程中,通過調用latch.await()來實現對計數器等於0的等待

2、進一步優化性能

getPOrders()和getDOrders()這兩個查詢操作和對賬操作check()、save()之間也是可以並行的,也就是說,在執行對賬操作的時候,可以同時去執行下一輪的查詢操作,如下圖所示
在這裏插入圖片描述
針對對賬這個項目,設計了兩個隊列,並且兩個隊列的元素之間還有對應關係。具體如下圖所示,訂單查詢操作將訂單查詢結果插入訂單隊列,派送單查詢操作將派送單插入派送單隊列,這兩個隊列的元素之間是有一一對應的關係的。兩個隊列的好處是,對賬操作可以每次從訂單隊列出一個元素,從派送單隊列出一個元素,然後對這兩個元素執行對賬操作,這樣數據一定不會亂掉
在這裏插入圖片描述
線程T1和線程T2只有都生產完1條數據的時候,才能一起向下執行,也就是說,線程T1和線程T2要互相等待,步調要一致;同時當線程T1和T2都生產完一條數據的時候,還要能夠通知線程T3執行對賬操作
在這裏插入圖片描述

3、用CyclicBarrier實現線程同步

	//訂單隊列
	Vector<P> pos;
	//派送單隊列
	Vector<D> dos;
	//執行回調的線程池
	Executor executor = Executors.newFixedThreadPool(1);
	final CyclicBarrier barrier = new CyclicBarrier(2, () -> {
		executor.execute(() -> check());
	});

	void check() {
		P p = pos.remove(0);
		D d = dos.remove(0);
		//執行對賬操作
		diff = check(p, d);
		//差異寫入差異庫
		save(diff);
	}

	void checkAll() {
		//循環查詢訂單庫
		Thread T1 = new Thread(() -> {
			while (存在未對賬訂單) {
				//查詢訂單庫
				pos.add(getPOrders());
				//等待
				barrier.await();
			}
		});
		T1.start();
		//循環查詢運單庫
		Thread T2 = new Thread(() -> {
			while (存在未對賬訂單) {
				//查詢運單庫
				dos.add(getDOrders());
				//等待
				barrier.await();
			}
		});
		T2.start();
	}

首先創建了一個計數器初始值爲2的CyclicBarrier,還傳入了一個回調函數,當計數器減到0的時候,會調用這個回調函數

線程T1負責查詢訂單,當查出一條時,調用barrier.await()來將計數器減1,同時等待計數器變爲0;線程T2負責查詢派送單,當查出一條時,也調用barrier.await()來將計數器減1,同時等待計數器變爲0;當T1和T2都調用barrier.await()的時候,計數器會減到0,此時T1和T2就可以執行下一條語句了,同時會調用barrier的回調函數來執行對賬操作

CyclicBarrier的計數器有自動重置的功能,當減到0的時候,會自動重置設置的初始值

回調函數中使用了一個固定大小爲1的線程池。首先使用線程池是爲了異步操作,否則回調函數是同步調用的,也就是本次對賬操作執行完才能進行下一輪的檢查;其次線程數量固定爲1,防止了多線程併發導致的數據不一致,因爲訂單和派送單是兩個隊列,只有單線程去兩個隊列中取消息纔不會出現消息不匹配的問題

4、總結

CountDownLatch主要用來解決一個線程等待多個線程的場景,而CyclicBarrier是一組線程之間互相等待,而且CyclicBarrier的計數器具備自動重置的功能,可以循環利用,CyclicBarrier還可以設置回調函數

四、信號量Semaphore

1、信號量模型

信號量模型包括一個計數器,一個等待隊列,三個方法。在信號量模型裏,計數器和等待隊列對外是透明的,所以只能通過信號量模型提供的三個方法來訪問它們,這三個方法分別是:init()、down()和up()
在這裏插入圖片描述

  • init():設置計數器的初始值
  • down():計數器的值減1;如果此時計數器的值小於0,則當前線程將被阻塞,否則當前線程可以繼續執行
  • up():計數器的值加1;如果此時計數器的值小於或者等於0,則喚醒等待隊列中的一個線程,並將其從等待隊列中移除

init()、down()和up()三個方法都是原子性的。在Java SDK裏面,信號量模型是由java.util.concurrent.Semaphore實現的,Semaphore這個類能夠保證這三個方法都是原子操作

信號量模型裏面down()、up()這兩個操作歷史上最早稱爲P操作和V操作,所以心好累模型也被稱爲PV原語。在Semaphore中,down()和up()對應的則是acquire()和release()

2、Semaphore使用

1)、使用Semaphore實現累加器(互斥)

    static int count;
    //初始化信號量
    static final Semaphore semaphore = new Semaphore(1);

    //用信號量保證互斥    
    static void addOne() {
        try {
            semaphore.acquire();
            count += 1;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }

假設兩個線程T1和T2同時訪問addOne()方法,當它們同時調用acquire()的時候,由於acquire()是一個原子操作,所以只能由一個線程(假設T1)把信號量裏的計數器減爲0,另外一個線程(T2)則是將計數器減爲-1。對於線程T1,信號量裏面的計數器的值是0,大於等於0,所以線程T1會繼續執行;對於線程T2,信號量裏面的計數器的值是-1,小於0,按照信號量模型裏的對down()操作的描述,線程T2將被阻塞。所以此時只有線程T1會進入臨界區執行count += 1

當線程T1執行release()操作,也就是up()操作的時候,信號量裏計數器的值是-1,加1之後的值是0,小於等於0,按照信號量模型裏對up()操作的描述,此時等待隊列中的T2將會被喚醒。於是T2在T1執行完臨界區代碼之後才獲得了進入臨界區執行的機會,從而保證了互斥性

2)、使用Semaphore控制同時訪問特定資源的線程數量

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(8);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "開始執行");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}

運行結果:第一次有8個線程執行了打印,等待5秒後,後兩個線程執行了打印

3)、使用Semaphore實現一個對象池

public class ObjPool<T, R> {
	final List<T> pool;
	//用信號量實現限流器
	final Semaphore sem;

	//構造函數
	ObjPool(int size, T t) {
		pool = new Vector<T>();
		for (int i = 0; i < size; i++) {
			pool.add(t);
		}
		sem = new Semaphore(size);
	}

	//利用對象池的對象,調用func
	R exec(Function<T, R> func) throws InterruptedException {
		T t = null;
		sem.acquire();
		try {
			t = pool.remove(0);
			return func.apply(t);
		} finally {
			pool.add(t);
			sem.release();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		//創建對象池
		ObjPool<Long, String> pool = new ObjPool<Long, String>(10, 2L);
		//通過對象池獲取t,之後執行
		pool.exec(t -> {
			System.out.println(t);
			return t.toString();
		});
	}
}

Semaphore可以允許多個線程訪問一個臨界區,用Vector保存對象實例,Vector是線程安全的,用Semaphore 實現限流器。關鍵的代碼是ObjPool裏面的exec()方法,這個方法裏面實現了限流的功能。在這個方法裏面,我們首先調用acquire()方法(與之匹配的是在finally裏面調用release()方法),假設對象池的大小是10,信號量的計數器初始化爲10,那麼前10個線程調用acquire()方法,都能繼續執行,而其他線程則會阻塞在acquire()方法上。對於通過信號燈的線程,我們爲每個線程分配了一個對象t(這個分配工作是通過pool.remove(0)實現的),分配完之後會執行一個回調函數func,而函數的參數正是前面分配的對象t;執行完回調函數之後,它們就會釋放對象(這個釋放工作是通過pool.add(t)實現的),同時調用release()方法來更新信號量的計數器。如果此時信號量裏計數器的值小於等於0,那麼說明有線程在等待,此時會自動喚醒等待的線程

3、Semaphore源碼分析

在這裏插入圖片描述
Semaphore還是使用AQS實現的。Sync只是對AQS的一個修飾,並且Sync有兩個實現類,用來指定獲取信號量時是否採用公平策略

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
        Sync(int permits) {
            setState(permits);
        }

Semaphore默認釆用非公平策略,如果需要使用公平策略則可以使用帶兩個參數的構造函數來構造Semaphore對象

如CountDownLatch構造函數傳遞 的初始化信號量個數permits被賦給了AQS的state狀態變量一樣,這裏AQS的state值表示當前持有的信號量個數

1)、acquire()方法

    public void acquire() throws InterruptedException {
        //調用AQS中的模板方法acquireSharedInterruptibly
        sync.acquireSharedInterruptibly(1);
    }

AQS中的acquireSharedInterruptibly方法:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //調用Semaphore中sync重寫的tryAcquireShared方法,根據構造函數確定使用的公平策略
        if (tryAcquireShared(arg) < 0)
            //如果獲取失敗則放入阻塞隊列。然後再次嘗試,如果失敗則調用park方法掛起當前線程
            doAcquireSharedInterruptibly(arg);
    }

非公平策略:

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                //獲取當前信號量值
                int available = getState();
                //計算當前剩餘值
                int remaining = available - acquires;
                //如果當前剩餘值小於0或者CAS設置成功則返回
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

如果線程A先調用了aquire()方法獲取信號量,但是當前信號量個數爲0,那麼線程A會被放入AQS的阻塞隊列。過一段時間後線程C調用了release()方法釋放了一個信號量,如果當前沒有其他線程獲取信號量,那麼線程A就會被激活,然後獲取該信號量,但是假如線程C釋放信號量後,線程C調用了aquire()方法,那麼線程C就會和線程A去競爭這個信號量資源。如果採用非公平策略,線程C完全可以在線程A被激活前,或者激活後先於線程A獲取到該信號量,也就是在這種模式下阻塞線程和當前請求的線程是競爭關係,而不遵循先來先得的策略

公平策略:

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                //公平策略是看當前線程節點的前驅節點是否也在等待獲取該資源,如果是則自己放棄獲取的權限, 然後當前線程會被放入AQS阻塞隊列,否則就去獲取
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

2)、release()方法

    public void release() {
        //調用AQS中的模板方法releaseShared
        sync.releaseShared(1);
    }

AQS中的releaseShared方法:

    public final boolean releaseShared(int arg) {
        //嘗試釋放資源
        if (tryReleaseShared(arg)) {
            //釋放資源成功則調用park方法喚醒AQS隊列裏面最先掛起的線程
            doReleaseShared();
            return true;
        }
        return false;
    }

Sync中重寫的tryReleaseShared方法:

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                //獲取當前信號量值
                int current = getState();
                //將當前信號量值增加releases
                int next = current + releases;
                if (next < current) 
                    throw new Error("Maximum permit count exceeded");
                //使用CAS保證更新信號量值的原子性
                if (compareAndSetState(current, next))
                    return true;
            }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章