(三)JDK并發(fā)包——鎖

synchronized可以用于控制一個線程是否可以訪問臨界區(qū)資源,Object.wait()Object.notify()方法可以實現(xiàn)線程等待和通知。這些工具都很簡單可靠,但是想要實現(xiàn)更復雜和高級的功能,就要用到Java中的鎖。

1.重入鎖(ReentrantLock)

  • lock.lock()簡單的上鎖
    重入鎖完全可以替代synchronized關鍵字,在Java早期版本中重入鎖的性能遠遠優(yōu)于synchronized,而從JDK1.6開始,synchronized進行了大量的優(yōu)化,兩者性能不相上下。
public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args){
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lock.lock();
                lock.lock();
                try {
                    System.out.println(LocalDateTime.now());
                } finally {
                    lock.unlock();
                    lock.unlock();
                }
            }).start();
        }
    }
}

重入鎖如它的名字一樣,一個線程可以連續(xù)兩次獲得同一把鎖,(否則線程會在第二次請求鎖時和自己產(chǎn)生死鎖),同樣,多次獲得鎖之后也要多次釋放鎖,否則其他線程將無法獲取鎖。

  • lock.lockInterruptibly()響應中斷
public class Main {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args){
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
            t1.start();
            t2.start();
            t2.interrupt();
    }
    
    static class MyThread extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lockInterruptibly();
                    System.out.println(LocalDateTime.now());
                } catch (InterruptedException e) {
                    System.out.println("break");
                    break;
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

使用lock.lockInterruptibly()可以使線程在等待鎖時響應中斷,此時線程會拋出一個InterruptedException異常并放棄鎖的競爭。

  • lock.tryLock()超時結束
public boolean tryLock()
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException 

無參數(shù)的lock.tryLock()方法會在調(diào)用后嘗試獲得鎖,如果成功立刻返回true,失敗立刻返回false。而有參數(shù)的方法可以持續(xù)請求一段時間后自動退出請求并返回false,同時有參數(shù)的lock.tryLock()同樣可以響應中斷。

  • new ReentrantLock(true)公平鎖
    使用synchronized關鍵字進行鎖控制,產(chǎn)生的鎖就是非公平鎖,即在分配鎖時不會管線程請求鎖的時間先后,所有線程都有可能分配到鎖。而公平鎖則按照請求鎖的時間先后分配鎖,這保證了不會出現(xiàn)饑餓現(xiàn)象,但公平鎖需要維護一個有序的請求隊列,因此開銷更大,性能較低。
public ReentrantLock(boolean fair)
  • 重入鎖的實現(xiàn)
    第一,原子狀態(tài)。原子狀態(tài)使用CAS操作來存儲當前鎖的狀態(tài),判斷鎖是否已經(jīng)被其他線程持有。
    第二,等待隊列。所有沒有請求到鎖的線程,會進入等待隊列中進行等待。待有線程釋放鎖之后,系統(tǒng)就能從等待隊列中喚醒一個線程,繼續(xù)工作。
    第三,阻塞原語(park)和(unpark),用來掛起和恢復線程。沒有得到鎖的線程將被掛起。

2.Condition接口

Condition的作用類似于Object.wait()Object.notify(),不過Condition是用于和ReentrantLock合作。它有以下方法。

// await()方法會使當前線程等待,同時釋放當前鎖,當其他線程使用
// signal()或signalAll()方法時,線程會重新獲得鎖并繼續(xù)執(zhí)行?;蛘?// 當線程被中斷時,也能跳出等待。功能上類似于Object.wait()方法。
void await() throws InterruptedException;
// 與await()方法類似,但是并不響應中斷
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
// 用于喚醒一個在等待中的線程,類似于Object.notify()
void signal();
void signalAll();

類似于Object.wait()Object.notify(),執(zhí)行前必須用synchronized獲得Object對象的鎖。Condition對象由鎖的newCondition()方法生成,使用await()signal()方法前線程必須獲得鎖對象,而使用后要釋放鎖對象。

public Condition newCondition() { return sync.newCondition(); }

3.信號量(Semaphore)

信號量是對鎖的拓展,可以允許多個線程同時訪問臨界區(qū)資源。

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

構造信號量時,必須要指定信號量的準入數(shù),還可以指定是否公平分配信號量。

// 嘗試獲取信號量,獲取時可以響應中斷
public void acquire() throws InterruptedException 
// 嘗試獲取信號量,獲取時不響應中斷
public void acquireUninterruptibly()
// 嘗試獲取信號量,立刻返回結果,成功為true,失敗為false
public boolean tryAcquire()
// 嘗試獲取信號量一段時間,可以響應中斷
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException 
// 釋放占有的信號量
public void release()

信號量的方法與重入鎖基本類似,區(qū)別只有同時進入臨界區(qū)的線程數(shù)量。

4. 讀寫鎖(ReadWriteLock)

如果使用synchronized關鍵字或者重入鎖,則所有讀與讀之間、讀與寫之間、寫與寫之間都是串行操作,而讀寫鎖允許多個線程同時讀,寫寫和讀寫之間依然相互排斥。如果系統(tǒng)中的讀遠大于寫,則讀寫鎖可以很好的提升系統(tǒng)性能。

ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();

使用時,從ReadWriteLock上分別獲得讀鎖和寫鎖,讀寫操作時分別請求對應的鎖。

5.倒計時器(CountDownLatch)

倒計時器可以讓某個線程等待直到計數(shù)器歸零。


public class Main {
    private static CountDownLatch count = new CountDownLatch(10);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt());
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // todo
    }
}

countDownLatch.await()方法同樣可以響應中斷。

6.循環(huán)柵欄(CyclicBarrier)

循環(huán)柵欄類似于倒計時器但功能更多一些,首先循環(huán)柵欄可以重復觸發(fā),另外可以接受一個Runnable對象,作為一次計數(shù)完成后系統(tǒng)會觸發(fā)的動作。

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction) 
public int await() throws InterruptedException, BrokenBarrierException 

在使用時,每有一個線程執(zhí)行CyclicBarrier.await(),程序計數(shù)加一,到達指定數(shù)后就會觸發(fā)barrierAction。CyclicBarrier.await()會返回線程到達的名次,最后一個到達的將會返回0。CyclicBarrier.await()可以響應中斷,而當線程已經(jīng)不可能滿足程序計數(shù)時(比如某個線程被中斷),則其余線程會拋出BrokenBarrierException異常。

7.線程阻塞工具類(LockSupport)

LockSupport可以在線程中的任意位置讓線程阻塞,與Thread.suspend()方法相比,彌補了由于resume()方法發(fā)生導致線程無法繼續(xù)執(zhí)行的情況;與Object.wait()方法相比,不需要先獲得某個對象的鎖,也不會拋出InterruptedException異常。

public static void park(Object blocker) 
public static void unpark(Thread thread) 

即使unpark()方法發(fā)生在park()方法之前,它也能使下一次的park()方法立即返回,同時,處于park()方法掛起狀態(tài)的線程不會像Thread.suspend()方法一樣顯示Runnable狀態(tài),而是明確的Waiting狀態(tài),并且還會標注是由park()方法引起的。

8.限流(RateLimiter)

限流算法的思路:

  • 最簡單的限流算法就是給出一個單位時間,然后使用一個計數(shù)器count統(tǒng)計單位時間內(nèi)收到的請求數(shù)量,當請求數(shù)量超過門限時,余下的請求丟棄或等待。
  • 漏桶算法:利用一個緩沖區(qū),無論請求的速率如何,都先進入緩沖區(qū)等待,然后以固定的流速離開緩沖區(qū)。
  • 令牌桶算法:系統(tǒng)以一定的速率生成令牌并存入令牌桶中,桶中只能存放一定時限內(nèi)的令牌。當有請求到來時,拿走桶中的一個令牌,如果桶中沒有令牌,則等待或丟棄請求。

RateLimiter就是是Google旗下一個庫Guava中的一個工具,采用了令牌桶算法來控制流量??梢苑乐惯^量的請求創(chuàng)建的線程壓垮服務器。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容