# 雙重檢查鎖定模式 (Double-Checked Locking Pattern)

> 雙重檢查鎖定 (Double-Checked Locking Pattern) 是另外一個常用的設計模式，用來減少並發系統中競爭和同步的開銷。雙重檢查鎖定模式首先驗證鎖定條件(第一次檢查)，只有通過鎖定條件驗證才真正的進行加鎖邏輯並再次驗證條件(第二次檢查)。常用來避免快取在同一時間被重複建立。

### 定義

我們先來看看來自 Wiki 的定義

> 雙重檢查鎖定模式（也被稱為”雙重檢查加鎖優化”，”鎖暗示”（Lock hint）\[1]) 是一種軟體設計模式用來減少並發系統中競爭和同步的開銷。雙重檢查鎖定模式首先驗證鎖定條件(第一次檢查)，只有通過鎖定條件驗證才真正的進行加鎖邏輯並再次驗證條件(第二次檢查)。 該模式在某些語言在某些硬體平台的實現可能是不安全的。有的時候，這一模式被看做是反模式。 它通常用於減少加鎖開銷，尤其是為多執行緒環境中的單例模式實現「惰性初始化」。惰性初始化的意思是直到第一次訪問時才初始化它的值。

### C# 實際範例

在一般的情境下，我最常使用在快取建立的情境，在快取要建立前，使用 Double-Checked Locking 防止快取的重複建立。

#### 使用快取範例

先來說明 ASP.NET Core 下如何使用快取，範例是使用 WebApi 專案範本。

Startup 註冊 MemoryCache

```c#
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register MemoryCache
        services.AddMemoryCache();

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}
```

再來 Contorller 以下設定

```c#
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace DoubleCheckedLocking.WebApplication.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private IMemoryCache _cache;

        public ValuesController(IMemoryCache memoryCache)
        {
            this._cache = memoryCache;
        }

            // 取得快取值
            var cacheEntry = this._cache.Get<DateTime>("key");

            // 快取檢查
            if (this._cache.Get<DateTime>("key") == null)
            {
                // 無快取, 所以重新取值
                cacheEntry = DateTime.Now;

                // 設定快取過期時間
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromSeconds(3));

                // 加入快取
                _cache.Set("key", cacheEntry, cacheEntryOptions);
            }

            return cacheEntry.ToString();
        }
    }
}
```

#### 會遇到的問題

當大量連線請求執行這段程式碼的時候，是有可能在極短暫時間下，快取不存在的鎖定判斷會被通過多次請求，而導致快取被重複建立，而當快取建立的成本很高的時候(比如說連接資料庫)，更可能造成後端的負載壓力，在筆者工作環境中，是個同時上線人數達 2000 人的大系統是，屢屢遇到快取鎖定失效導致資料庫負擔過重的案例，這時候 Double-Checked Locking 便派上用場，讓我們將快取的程式碼繼續導入 Double-Checked Locking。

```c#
    // GET api/values
    [HttpGet]
    public ActionResult<string> Get()
    {
        // 取得快取值
        var cacheEntry = this._cache.Get<DateTime>("key");

        // 第一次檢查
        if (cacheEntry == null)
        {
            // 鎖定
            lock (padlock)
            {
                // 第二次檢查
                if (this._cache.Get<DateTime>("key") == null)
                {
                    // 無快取, 所以重新取值
                    cacheEntry = DateTime.Now;

                    // 設定快取過期時間
                    var cacheEntryOptions = new MemoryCacheEntryOptions()
                        .SetAbsoluteExpiration(TimeSpan.FromSeconds(3));

                    // 加入快取
                    _cache.Set("key", cacheEntry, cacheEntryOptions);
                }
            }
        }

        return cacheEntry.ToString();
    }
}
```

這裡要注意的是，第二次的檢查務必重新取得快取來判斷，若繼續沿用第一次檢查的變數，將會導致無效檢查。

### 結語

Double-Checked Locking 在快取情境下使用非常適合，另外一個適用情境是單例模式 (Singleton Pattern)，之後講到 Singleton 再說明囉。

[程式碼範例](https://github.com/raychiutw/double-checked-locking-pattern)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kerryhuangs-organization.gitbook.io/kerry-de-bi-ji-ben/devops/shuang-zhong-jian-cha-suo-ding-mo-shi-doublechecked-locking-pattern.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
