91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java并發編程中如何通過ReentrantLock和Condition實現銀行存取款

發布時間:2021-11-20 15:15:13 來源:億速云 閱讀:155 作者:柒染 欄目:云計算

本篇文章為大家展示了java并發編程中如何通過ReentrantLock和Condition實現銀行存取款,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

java.util.concurrent.locks包為鎖和等待條件提供一個框架的接口和類,它不同于內置同步和監視器。該框架允許更靈活地使用鎖和條件,但以更難用的語法為代價。 

        Lock 接口支持那些語義不同(重入、公平等)的鎖規則,可以在非阻塞式結構的上下文(包括 hand-over-hand 和鎖重排算法)中使用這些規則。主要的實現是 ReentrantLock。 

        ReadWriteLock 接口以類似方式定義了一些讀取者可以共享而寫入者獨占的鎖。此包只提供了一個實現,即 ReentrantReadWriteLock,因為它適用于大部分的標準用法上下文。但程序員可以創建自己的、適用于非標準要求的實現。 

   以下是locks包的相關類圖:

java并發編程中如何通過ReentrantLock和Condition實現銀行存取款

        在之前我們同步一段代碼或者對象時都是使用 synchronized關鍵字,使用的是Java語言的內置特性,然而 synchronized的特性也導致了很多場景下出現問題,比如:

        在一段同步資源上,首先線程A獲得了該資源的鎖,并開始執行,此時其他想要操作此資源的線程就必須等待。如果線程A因為某些原因而處于長時間操作的狀態,比如等待網絡,反復重試等等。那么其他線程就沒有辦法及時的處理它們的任務,只能無限制的等待下去。如果線程A的鎖在持有一段時間后可自動被釋放,那么其他線程不就可以使用該資源了嗎?再有就是類似于數據庫中的共享鎖與排它鎖,是否也可以應用到應用程序中?所以引入Lock機制就可以很好的解決這些問題。

  Lock提供了比 synchronized更多的功能。但是要注意以下幾點:

  ? Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;

  ? Lock和synchronized有一點非常大的不同,采用 synchronized不需要用戶去手動釋放鎖,當synchronized方法或者 synchronized代碼塊執行完之后,系統會自動讓線程釋放對鎖的占用;而 Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

一、可重入鎖 ReentrantLock

  想到鎖我們一般想到的是同步鎖即 Synchronized,這里介紹的可重入鎖ReentrantLock的效率更高。IBM對于可重入鎖進行了一個介紹:JDK 5.0 中更靈活、更具可伸縮性的鎖定機制

  這里簡單介紹下可重入鎖的分類:(假設線程A獲取了鎖,現在A執行完成了,釋放了鎖同時喚醒了正在等待被喚醒的線程B。但是,A執行喚醒操作到B真正獲取鎖的時間里可能存在線程C已經獲取了鎖,造成正在排隊等待的B無法獲得鎖)

  1) 公平鎖: 

     由于B先在等待被喚醒,為了保證公平性原則,公平鎖會先讓B獲得鎖。

  2) 非公平鎖

     不保證B先獲取到鎖對象。

  這兩種鎖只要在構造ReentrantLock對象時加以區分就可以了,當參數設置為true時為公平鎖,false時為非公平鎖,同時默認構造函數也是創建了一個非公平鎖。

    private Lock lock = new ReentrantLock(true); ReentrantLock的公平鎖在性能和實效性上作了很大的犧牲,可以參考IBM上發的那篇文章中的說明。

二、條件變量 Condition

  Condition是java.util.concurrent.locks包下的一個接口,  Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。需要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。為了避免兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不同。 

       Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。 

   Condition(也稱為條件隊列 或條件變量)為線程提供了一種手段,在某個狀態條件下直到接到另一個線程的通知,一直處于掛起狀態(即“等待”)。因為訪問此共享狀態信息發生在不同的線程中,所以它必須受到保護,因此要將某種形式的鎖與 Condition相關聯。

        Condition 實例實質上被綁定到一個鎖上。

  這里不再對Locks包下的源碼進行分析。

三、ReentrantLock和Condition設計多線程存取款

1. 存款的時候,不能有線程在取款 。取款的時候,不能有線程在存款。

2. 取款時,余額大于取款金額才能進行取款操作,否則提示余額不足。

3.  當取款時,如果金額不足,則阻塞當前線程,并等待2s(可能有其他線程將錢存入)。

    如果2s之內沒有其它線程完成存款,或者還是金額不足則打印金額不足。

    如果其它存入足夠金額則通知該阻塞線程,并完成取款操作。

/**
 * 普通銀行賬戶,不可透支
 */
public class MyCount {
    private String oid; // 賬號
    private int cash;   // 賬戶余額
    //賬戶鎖,這里采用公平鎖,掛起的取款線程優先獲得鎖,而不是讓其它存取款線程獲得鎖
    private Lock lock = new ReentrantLock(true);
    private Condition _save = lock.newCondition(); // 存款條件
    private Condition _draw = lock.newCondition(); // 取款條件

    MyCount(String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }

    /**
     * 存款
     * @param x 操作金額
     * @param name 操作人
     */
    public void saving(int x, String name) {
        lock.lock(); // 獲取鎖
        if (x > 0) {
            cash += x; // 存款
            System.out.println(name + "存款" + x + ",當前余額為" + cash);
        }
        _draw.signalAll(); // 喚醒所有等待線程。
        lock.unlock(); // 釋放鎖
    }

    /**
     * 取款
     * @param x  操作金額
     * @param name 操作人
     */
    public void drawing(int x, String name) {
        lock.lock(); // 獲取鎖
        try {
            if (cash - x < 0) {
                System.out.println(name + "阻塞中");
                _draw.await(2000,TimeUnit.MILLISECONDS); // 阻塞取款操作, await之后就隱示自動釋放了lock,直到被喚醒自動獲取
            }
            if(cash-x>=0){
                cash -= x; // 取款
                System.out.println(name + "取款" + x + ",當前余額為" + cash);
            }else{
                System.out.println(name+" 余額不足,當前余額為 "+cash+"   取款金額為 "+x);
            }
            // 喚醒所有存款操作,這里并沒有什么實際作用,因為存款代碼中沒有阻塞的操作
            _save.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 釋放鎖
        }
    }
}

這里的可重入鎖也可以設置成非公平鎖,這樣阻塞取款線程可能后與其它存取款操作。

/**
     * 存款線程類
     */
    static class SaveThread extends Thread {
        private String name; // 操作人
        private MyCount myCount; // 賬戶
        private int x; // 存款金額

        SaveThread(String name, MyCount myCount, int x) {
            this.name = name;
            this.myCount = myCount;
            this.x = x;
        }

        public void run() {
            myCount.saving(x, name);
        }
    }

    /**
     * 取款線程類
     */
    static class DrawThread extends Thread {
        private String name; // 操作人
        private MyCount myCount; // 賬戶
        private int x; // 存款金額

        DrawThread(String name, MyCount myCount, int x) {
            this.name = name;
            this.myCount = myCount;
            this.x = x;
        }

        public void run() {
            myCount.drawing(x, name);
        }
    }

    public static void main(String[] args) {
        // 創建并發訪問的賬戶
        MyCount myCount = new MyCount("95599200901215522", 1000);
        // 創建一個線程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Thread t1 = new SaveThread("S1", myCount, 100);
        Thread t2 = new SaveThread("S2", myCount, 1000);
        Thread t3 = new DrawThread("D1", myCount, 12600);
        Thread t4 = new SaveThread("S3", myCount, 600);
        Thread t5 = new DrawThread("D2", myCount, 2300);
        Thread t6 = new DrawThread("D3", myCount, 1800);
        Thread t7 = new SaveThread("S4", myCount, 200);
        // 執行各個線程
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 關閉線程池
        pool.shutdown();
    }
}

上述類中定義了多個存取款的線程,執行結果如下:

S1存款100,當前余額為1100
S3存款600,當前余額為1700
D2阻塞中
S2存款1000,當前余額為2700
D2取款2300,當前余額為400
D3阻塞中
S4存款200,當前余額為600
D3 余額不足,當前余額為 600   取款金額為 1800
D1阻塞中
D1 余額不足,當前余額為 600   取款金額為 12600

執行步驟如下:

  1. 初始化賬戶,有余額100。

  2. S1,S3完成存款。

  3. D2取款,余額不足,釋放鎖并阻塞線程,進入等待隊列中。

  4.  S2完成存款操作后,會喚醒掛起的線程,這時D2完成了取款。

  5.  D3取款,余額不足,釋放鎖并阻塞線程,進入等待隊列中。

  6.  S4完成存款操作后,喚醒D3,但是依然余額不足,D3 取款失敗。

  7.  D1 進行取款,等待2s鐘,無任何線程將其喚醒,取款失敗。

這里需要注意的是,當Condition調用await()方法時,當前線程會釋放鎖(否則就和Sychnize就沒有區別了)

將銀行賬戶中的 鎖改成非公平鎖時,執行的結果如下:

1存款100,當前余額為1100
S3存款600,當前余額為1700
D2阻塞中
S2存款1000,當前余額為2700
D3取款1800,當前余額為900
D2 余額不足,當前余額為 900   取款金額為 2300
S4存款200,當前余額為1100
D1阻塞中
D1 余額不足,當前余額為 1100   取款金額為 12600

D2 取款出現余額不足后釋放鎖,進入等待狀態。但是當S2線程完成存款后并沒有立刻執行D2線程,而是被D3插隊了。

通過執行結果可以看出 公平鎖和非公平鎖的區別,公平鎖能保證等待線程優先執行,但是非公平鎖可能會被其它線程插隊。

四、ArrayBlockingQueue中關于ReentrantLock和Condition的應用

JDK源碼中關于可重入鎖的非常典型的應用是 BlockingQueue,從它的源碼中的成員變量大概就能知道了(ArrayBlockingQueue為例):

/** The queued items */
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** Main lock guarding all access */
// 主要解決多線程訪問的線程安全性問題
    final ReentrantLock lock;

    /** Condition for waiting takes */
 // 添加元素時,通過notEmpty 喚醒消費線程(在等待該條件)
    private final Condition notEmpty;

    /** Condition for waiting puts */
 // 刪除元素時,通過 notFull 喚醒生成線程(在等待該條件)
    private final Condition notFull;

ArrayBlockingQueue 是一個典型的生產者消費者模型,通過一個數組保存元素。為了保證添加和刪除元素的線程安全性,增加了可重入鎖和條件變量。

可重入鎖主要保證多線程對阻塞隊列的操作是線程安全的,同時為了讓被阻塞的消費者或者生產者能夠被自動喚醒,這里引入了條件變量。

java并發編程中如何通過ReentrantLock和Condition實現銀行存取款

當隊列已滿時,Producer會被阻塞,此時如果Customer消費一個元素時,被阻塞的Producer就會被自動喚醒并往隊列中添加元素。

上面的兩個例子可見java.util.concurrent.locks包下的ReentrantLock和Condition配合起來的靈活性及實用性。

上述內容就是java并發編程中如何通過ReentrantLock和Condition實現銀行存取款,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

浏阳市| 仙居县| 两当县| 上饶市| 永川市| 开阳县| 林周县| 鄂托克前旗| 顺平县| 新疆| 兴隆县| 孝义市| 肥乡县| 苍溪县| 漯河市| 七台河市| 西盟| 峡江县| 迁安市| 新丰县| 乌兰浩特市| 江孜县| 溆浦县| 和平区| 武平县| 师宗县| 甘谷县| 山阴县| 武川县| 芜湖县| 台前县| 仙桃市| 玉山县| 顺昌县| 古田县| 洪泽县| 郎溪县| 鄂伦春自治旗| 南阳市| 林西县| 报价|