多線程系列(九) -ReentrantLock常用方法詳解

来源:https://www.cnblogs.com/dxflqm/p/18033936
-Advertisement-
Play Games

在上一篇文章中,我們介紹了ReentrantLock類的一些基本用法,今天我們重點來介紹一下ReentrantLock其它的常用方法,以便對ReentrantLock類的使用有更深入的理解。 ...


一、簡介

在上一篇文章中,我們介紹了ReentrantLock類的一些基本用法,今天我們重點來介紹一下ReentrantLock其它的常用方法,以便對ReentrantLock類的使用有更深入的理解。

二、常用方法介紹

2.1、構造方法

ReentrantLock類有兩個構造方法,核心源碼內容如下:

/**
 * 預設創建非公平鎖
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
/**
 * fair為true表示是公平鎖,fair為false表示是非公平鎖
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

相比於synchronized同步鎖,ReentrantLock有一個很大的特點,就是開發人員可以手動指定採用公平鎖機制還是非公平鎖機制。

公平鎖:顧名思義,就是每個線程獲取鎖的順序是按照線程排隊的順序來分配的,最前面的線程總是最先獲取到鎖。

  • 優點:所有的線程都有機會得到資源
  • 缺點:公平鎖機制實現比較複雜,程式流程比較多,執行速度比較慢

非公平鎖:每個線程獲取鎖的順序是隨機的,並不會遵循先來先得的規則,任何線程在某時刻都有可能直接獲取並擁有鎖,之前介紹的synchronized其實就是一種非公平鎖

  • 優點:公平鎖機制實現相對比較簡單,程式流程比較少,執行速度比較快
  • 缺點:有可能某些線程一直拿不到鎖,導致餓死

ReentrantLock預設的構造方法是非公平鎖,如果想要構造公平鎖機制,只需要傳入true就可以了。

示例代碼如下:

public static void main(String[] args) {
    // 創建公平鎖實現機制
    Lock lock = new ReentrantLock(true);

    // 創建5個線程
    for (int i = 0; i < 5; i++) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 啟動了!");

                // 嘗試獲取鎖
                lock.lock();
                try {
                    System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 獲得鎖!");
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}

運行一下程式,結果如下:

ThreadName:Thread-0, 啟動了!
ThreadName:Thread-1, 啟動了!
ThreadName:Thread-0, 獲得鎖!
ThreadName:Thread-1, 獲得鎖!
ThreadName:Thread-2, 啟動了!
ThreadName:Thread-2, 獲得鎖!
ThreadName:Thread-3, 啟動了!
ThreadName:Thread-3, 獲得鎖!
ThreadName:Thread-4, 啟動了!
ThreadName:Thread-4, 獲得鎖!

從日誌上可以看到,啟動順序為0,1,2,3,4,獲取鎖的順序為0,1,2,3,4,啟動與獲取鎖的排隊機制一致。

假如我們構造方法裡面的把true改成false,也就是非公平鎖機制,在看看運行效果,結果如下:

ThreadName:Thread-1, 啟動了!
ThreadName:Thread-2, 啟動了!
ThreadName:Thread-1, 獲得鎖!
ThreadName:Thread-0, 啟動了!
ThreadName:Thread-2, 獲得鎖!
ThreadName:Thread-3, 啟動了!
ThreadName:Thread-3, 獲得鎖!
ThreadName:Thread-0, 獲得鎖!
ThreadName:Thread-4, 啟動了!
ThreadName:Thread-4, 獲得鎖!

從日誌上可以看到,啟動順序為1,2,0,3,4,獲取鎖的順序為1,2,3,0,4,線程啟用與獲取鎖的順序不一致。

從實際的運行結果看,非公平鎖要比公平鎖執行速度要快一些,當線程數越多的時候,效果越明顯。

2.2、核心方法

ReentrantLock類的核心方法就比較多了,如下表!

方法 描述
public void lock() 阻塞等待獲取鎖;不允許Thread.interrupt中斷,即使檢測到Thread.isInterrupted一樣會繼續嘗試
public void lockInterruptibly() 當前線程未被中斷,則獲取鎖;允許在等待時由其它線程調用等待線程的Thread.interrupt方法來中斷等待線程的等待而直接返回
public boolean tryLock() 嘗試申請一個鎖,在成功獲得鎖後返回true,否則,立即返回false
public boolean tryLock(long timeout, TimeUnit unit) 在一段時間內嘗試申請一個鎖,在成功獲得鎖後返回true,否則,立即返回false
public void unlock() 釋放鎖
public Condition newCondition() 條件實例,用於線程等待/通知模式
public int getHoldCount() 獲取當前線程持有此鎖的次數
public boolean isHeldByCurrentThread() 檢測是否被當前線程持有
public boolean isLocked() 查詢此鎖是否由任意線程持有
public final boolean isFair() 如果是公平鎖返回true,否則返回false
public final boolean hasQueuedThreads() 查詢是否有線程正在等待
public final boolean hasQueuedThread(Thread thread) 查詢給定線程是否正在等待獲取此鎖
public final int getQueueLength() 獲取正等待獲取此鎖的線程數
public boolean hasWaiters(Condition condition) 是否存在正在等待並符合相關給定條件的線程
public int getWaitQueueLength(Condition condition) 正在等待並符合相關給定條件的線程數量

雖然方法很多,但是實際上常用方法就那麼幾個,下麵我們主要抽一些常用的方法進行介紹。

2.2.1、tryLock 方法

lock()lockInterruptibly()tryLock()tryLock(long timeout, TimeUnit unit)這幾個方法,目的其實是一樣的,都是為了獲取鎖,只是針對不同的場景做了單獨的處理。

lock():阻塞等待獲取鎖,如果沒有獲取到會一直阻塞,即使檢測到Thread.isInterrupted一樣會繼續嘗試;

  • lockInterruptibly():同樣也是阻塞等待獲取鎖,稍有不同的是,允許在等待時由其它線程調用等待線程的Thread.interrupt方法來中斷等待線程的等待而直接返回
  • tryLock():表示嘗試申請一個鎖,在成功獲得鎖後返回true,否則,立即返回false,不會阻塞等待獲取鎖
  • tryLock(long timeout, TimeUnit unit):表示在一段時間內嘗試申請一個鎖,在成功獲得鎖後返回true,否則,立即返回false

其中tryLock(long timeout, TimeUnit unit)方法的應用最廣泛,因為它能防止程式發生死鎖,即使在一段時間內沒有獲取鎖,也會自動退出,不會一直阻塞。

我們可以看一個簡單的例子,如下!

public static void main(String[] args) {
    // 創建公平鎖實現機制
    Lock lock = new ReentrantLock();

    // 創建5個線程
    for (int i = 0; i < 5; i++) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                boolean flag = false;
                try {
                    // 嘗試3秒內獲取鎖
                    flag = lock.tryLock(3, TimeUnit.SECONDS);
                    if(flag){
                        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 獲取到鎖");
                        // 模擬進行5秒的業務操作
                        Thread.sleep(5000);
                    } else {
                        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (flag){
                        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 釋放對象");
                        lock.unlock();
                    }
                }
            }
        }).start();
    }
}

運行一下程式,結果如下:

ThreadName:Thread-0, 獲取到鎖
ThreadName:Thread-3, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-1, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-2, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-4, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-0, 釋放對象

可以很清晰的看到,非Thread-0線程嘗試了 3 秒沒有獲取到鎖,自動放棄;如果換成lock()方法進行獲取鎖,線程Thread-0如果不釋放鎖,其它線程會一直阻塞。

2.2.2、unlock 方法

unlock()方法也是常用方法,表示釋放鎖。當獲取到鎖之後,一定要手動釋放鎖,否則可能會造成其它程式執行出現問題,通常用在finally方法塊裡面。

// 阻塞等待獲取鎖
lock.lock();
try {
    // 業務操作...
} finally {
	// 一定要釋放鎖
    lock.unlock();
}
2.2.3、newCondition 方法

newCondition()方法,在上文中介紹過,ReentrantLockCondition結合,可以實現線程之間的等待/通知模型。

簡單的示例,如下!

public class Counter {

    private final Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    private int count;

    public void await(){
        // 加鎖
        lock.lock();
        try {
            // 讓當前線程進入等待狀態,並釋放鎖
            condition.await();
            System.out.println("await等待結束,count:" + getCount());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }


    public void signal(){
        // 加鎖
        lock.lock();
        try {
            count++;
            // 喚醒某個等待線程
            condition.signal();
            System.out.println("signal 喚醒通知完畢");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}
public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // 先啟動執行等待的線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.await();
            }
        }).start();

        Thread.sleep(3000);

        // 過3秒,再啟動執行通知的線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.signal();
            }
        }).start();
    }
}

運行一下程式,結果如下:

signal 喚醒通知完畢
await等待結束,count:1
2.2.4、getHoldCount 方法

getHoldCount()方法的作用是返回的是當前線程調用lock()的次數。

示例代碼如下:

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // 第一次獲取鎖
            lock.lock();
            try {
                System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" +  lock.getHoldCount());

                // 第二次獲取鎖
                lock.lock();
                try {
                    System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" +  lock.getHoldCount());
                } finally {
                    lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }
    }).start();
}

運行一下程式,結果如下:

ThreadName:Thread-0, getHoldCount:1
ThreadName:Thread-0, getHoldCount:2

側面也證明瞭一點,ReentrantLocksynchronized一樣,鎖都具有可重入特性,也就是說同一個線程多次調用同一個ReentrantLocklock()方法,可以再次進入方法體,無需阻塞等待。

2.2.5、isLocked 方法

isHeldByCurrentThread()isLocked()方法都是用於檢測鎖是否被持有。

其中isHeldByCurrentThread()方法表示此鎖是否由當前線程持有;isLocked()方法表示此鎖是否由任意線程持有。

我們看一個簡單的示例,如下:

public class Counter {

    private ReentrantLock lock = new ReentrantLock();

    public void methodA(){
        lock.lock();
        try {
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 當前線程是否持有鎖:" +  lock.isHeldByCurrentThread());
            System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意線程是否持有鎖:" +  lock.isLocked());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void methodB(){
        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 當前線程是否持有鎖:" +  lock.isHeldByCurrentThread());
        System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意線程是否持有鎖:" +  lock.isLocked());
    }
}
public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.methodA();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.methodB();
            }
        }).start();
    }
}

運行一下程式,結果如下:

ThreadName:Thread-0, 當前線程是否持有鎖:true
ThreadName:Thread-0, 任意線程是否持有鎖:true
ThreadName:Thread-1, 當前線程是否持有鎖:false
ThreadName:Thread-1, 任意線程是否持有鎖:true

從日誌結果很容易理解,Thread-0線程持有鎖,因此調用isHeldByCurrentThread()isLocked()方法,返回結果都是trueThread-1線程沒有持有鎖,因此isHeldByCurrentThread()方法返回falseisLocked()方法返回true

2.2.6、isFair 方法

isFair()方法用來獲取此鎖是否公平鎖。

簡單的示例,如下:

ReentrantLock lock = new ReentrantLock(true);
System.out.println("是否公平鎖:" +  lock.isFair());

輸出結果如下:

是否公平鎖:true

ReentrantLock預設的是非公平鎖,當通過構造方法顯式傳入true時,採用的是公平鎖機制

2.2.5、hasQueuedThreads 方法

hasQueuedThreads()hasQueuedThread()方法都用於查詢是否有線程等待獲取鎖,稍有不同的是:hasQueuedThreads()方法表示查詢是否有線程正在等待獲取鎖;hasQueuedThread()方法表示查詢給定線程是否正在等待獲取此鎖。

另外還有一個getQueueLength()方法,表示獲取正等待獲取此鎖的線程數。

我們看一個簡單的示例,如下:

public static void main(String[] args) throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();

    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    });
    threadA.start();

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    });
    threadB.start();

    // 等待線程都啟動完畢
    Thread.sleep(1000);

    System.out.println("查詢是否有線程正在等待:" + lock.hasQueuedThreads());
    System.out.println("查詢處於等待的線程數:" + lock.getQueueLength());
    System.out.println("threadA 是否處於等待狀態:" +  lock.hasQueuedThread(threadA));
    System.out.println("threadB 是否處於等待狀態:" +  lock.hasQueuedThread(threadB));
}

輸出結果如下:

查詢是否有線程正在等待:true
查詢處於等待的線程數:1
threadA 是否處於等待狀態:false
threadB 是否處於等待狀態:true

從日誌上可以清晰的看到,線程threadA先獲取了鎖,線程threadB處於等待獲取鎖的狀態,處於等待的線程數為1

2.2.7、hasWaiters 方法

hasWaiters()getWaitQueueLength()方法,支持傳入condition條件對象進行查詢。

其中hasWaiters()方法表示查詢是否存在正在等待並符合相關給定條件的線程;getWaitQueueLength()方法表示查詢正在等待並符合相關給定條件的線程數量。

我們看一個簡單的示例,如下:

public static void main(String[] args) throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                condition.await();
                System.out.println("await等待結束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    });
    threadA.start();

    // 睡眠1秒
    Thread.sleep(1000);

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println("是否存在正在等待並符合相關給定條件的線程:" + lock.hasWaiters(condition));
                System.out.println("正在等待並符合相關給定條件的線程數量:" + lock.getWaitQueueLength(condition));
                Thread.sleep(5000);
                condition.signal();
                System.out.println("signal 喚醒通知完畢");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    });
    threadB.start();
}

輸出結果如下:

是否存在正在等待並符合相關給定條件的線程:true
正在等待並符合相關給定條件的線程數量:1
signal 喚醒通知完畢
await等待結束

需要註意的是,調用condition對象的方法,必須要在獲取鎖的方法體內執行。

三、小結

本文主要圍繞ReentrantLock類的核心方法進行了一些知識總結,其中最常用方法的主要就兩個,tryLock(long timeout, TimeUnit unit)unlock(),通過它可以實現線程同步安全的效果。

本文內容比較多,如果有不正之處,請多多諒解,並歡迎批評指出。

四、參考

1、https://www.cnblogs.com/xrq730/p/4855538.html


作者:程式員志哥
出處:pzblog.cn
資源:微信搜【程式員志哥】關註我,回覆 【技術資料】有我準備的一線程式必備電腦書籍、大廠面試資料和免費電子書。 希望可以幫助大家提升技術和能力。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 我知道自己現在的狀態很不好,以為放個假能好好放鬆下心情,結果昨晚做夢還在工作,調試代碼,和領導彙報工作。 天吶,明明是在放假,可大腦還在考慮工作的事,我的天那,這是怎麼了? Vue頁面參數傳遞 1、任務拆解 頁面跳轉時帶上當前電子書id參數ebookId 新增/編輯文檔時,讀取電子書id參 ...
  • 枚舉Enum是在多種語言中都有的一種數據類型,用於表示一組特定相關的常量數據集合,如性別(男、女)、數據狀態(可用、禁用)、垂直對齊(頂端、居中、底部)、星期等。特點是數據值固定,不會變,存儲和顯示的內容不同。然而在JavaScript中並沒有枚舉Enum類型,TypeScript算是有(本文中暫沒... ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、是什麼 許可權是對特定資源的訪問許可,所謂許可權控制,也就是確保用戶只能訪問到被分配的資源 而前端許可權歸根結底是請求的發起權,請求的發起可能有下麵兩種形式觸發 頁面載入觸發 頁面上的按鈕點擊觸發 總的來說,所有的請求發起都觸發自前端路由或 ...
  • 即使再小再簡單的需求,作為研發開發完畢之後,我們可以直接上線麽?其實很多時候事故往往就是由於“不以為意”發生的。事故的發生往往也遵循“墨菲定律”,這就要求我們更要敬畏線上,再小的需求點都需要經過嚴格的測試驗證才能上線。 ...
  • 指針和引用 當我們需要在程式中傳遞變數的地址時,可以使用指針或引用。它們都可以用來間接訪問變數,但它們之間有一些重要的區別。 指針是一個變數,它存儲另一個變數的地址。通過指針,我們可以訪問存儲在該地址中的變數。指針可以被重新分配,可以指向不同的變數,也可以為NULL。指針使用*運算符來訪問存儲在地址 ...
  • 與TXT文本文件,PDF文件更加專業也更適合傳輸,常用於正式報告、簡歷、合同等場合。項目中如果有使用Java將TXT文本文件轉為PDF文件的需求,可以查看本文中介紹的免費實現方法。 免費Java PDF庫 本文介紹的方法需要用到Free Spire.PDF for Java,該免費庫支持多種操作、轉 ...
  • 雲採用框架(Cloud Adoption Framework,簡稱CAF)為企業上雲提供策略和技術的指導原則和最佳實踐,幫助企業上好雲、用好雲、管好雲,併成功實現業務目標。本雲採用框架是基於服務大量企業客戶的經驗總結,將企業雲採用分為四個階段,並詳細探討企業應在每個階段採取的業務和技術策略;同時,還 ...
  • Excelize 是 Go 語言編寫的用於操作電子錶格辦公文檔的開源基礎庫,2024年2月26日,社區正式發佈了 2.8.1 版本,該版本包含了多項新增功能、錯誤修複和相容性提升優化。 ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...