{"id":2604,"date":"2026-07-08T12:37:28","date_gmt":"2026-07-08T22:37:28","guid":{"rendered":"https:\/\/mshaeri.com\/blog\/?p=2604"},"modified":"2026-07-10T05:19:31","modified_gmt":"2026-07-10T15:19:31","slug":"double-checked-locking-the-pattern-that-saves-performance","status":"publish","type":"post","link":"https:\/\/mshaeri.com\/blog\/double-checked-locking-the-pattern-that-saves-performance\/","title":{"rendered":"Double-Checked Locking, The Pattern That Saves Performance"},"content":{"rendered":"\n<p>In the early years of my career, when I first encountered double-checked locking in code, I thought it was a redundant code line checking same thing twice. I told myself why would anyone check the same condition twice? I asked a senior developer, and he explained how it ensure thread safety and save performance in concurrency. Actually, double-checked locking is a tiny but useful pattern that appears in concurrent programming.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Is Double-Checked Locking<\/h2>\n\n\n\n<p>At its core, double-checked locking is a technique that reduces the overhead of acquiring a lock by first checking the condition <strong>without<\/strong> synchronization\/locking in concurrent environment. Only if the condition appears to be true, we acquire the lock and check again. Its use cases are lazy singleton initialization, expensive resource initialization, optional application features, and memorized shared state .<\/p>\n\n\n\n<p>Here&#8217;s the canonical structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">def get_resource():\n    if resource is None:              # First check (no lock)\n        with lock:                    # Acquire lock\n            if resource is None:      # Second check (with lock)\n                resource = create()   # Expensive operation\n    return resource<\/code><\/pre>\n\n\n\n<p>But let&#8217;s step back and understand why this pattern exists.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Locks Are Expensive<\/h2>\n\n\n\n<p>In concurrent programming, locks are essential but costly. Every time you acquire and release a lock, you&#8217;re:<\/p>\n\n\n\n<ul>\n<li>Paying for system-level synchronization<\/li>\n\n\n\n<li>Potentially blocking other threads\/coroutines<\/li>\n\n\n\n<li>Introducing contention in your application<\/li>\n<\/ul>\n\n\n\n<p>Consider a web cache that stores expensive computation results:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class Cache:\n    def __init__(self):\n        self._lock = threading.Lock()\n        self._heavy_data = None\n\n    def get_data(self):\n        with self._lock:  # Every single request acquires the lock!\n            if self._heavy_data is None:\n                self._heavy_data = self._compute_heavy_data()\n            return self._heavy_data<\/code><\/pre>\n\n\n\n<p>In a high-traffic system, this lock becomes a bottleneck. Thousands of requests per second all waiting for the same lock, even after the data is cached.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Pattern in Action<\/h2>\n\n\n\n<p>Double-checked locking shines in scenarios where:<\/p>\n\n\n\n<ol>\n<li>An operation is <strong>expensive<\/strong> to perform<\/li>\n\n\n\n<li>The operation should be performed <strong>only once<\/strong><\/li>\n\n\n\n<li>The result is accessed <strong>frequently<\/strong><\/li>\n\n\n\n<li>The system is <strong>multi-threaded<\/strong><\/li>\n<\/ol>\n\n\n\n<p>Let&#8217;s look at some real-world applications:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Lazy Initialization of Resources<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class ConnectionPool:\n    _instance = None\n    _lock = threading.Lock()\n\n    def get_connection(self):\n        if self._connection is None:        # Quick check\n            with self._lock:                # Only lock if needed\n                if self._connection is None: # Double-check\n                    self._connection = self._create_connection()\n                    self._pool = self._initialize_pool()\n        return self._connection<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Expensive Computations<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class DataProcessor:\n    _cache = None\n    _lock = threading.Lock()\n\n    def get_processed_data(self):\n        if self._cache is None:                # Check without lock\n            with self._lock:                   # Acquire lock\n                if self._cache is None:        # Check with lock\n                    raw = self._load_massive_dataset()\n                    self._cache = self._process(raw)\n        return self._cache<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Lazy Loading in Web Applications<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class ConfigManager:\n    _config = None\n    _lock = threading.Lock()\n\n    def get_config(self):\n        if self._config is None:\n            with self._lock:\n                if self._config is None:\n                    self._config = self._load_config_from_file()\n                    self._config = self._parse_and_validate()\n        return self._config<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Performance Impact: Let&#8217;s Talk Numbers<\/h2>\n\n\n\n<p>I ran some benchmarks to illustrate the difference. Here&#8217;s what I found with 10,000 concurrent requests:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Pattern<\/th><th>Time (ms)<\/th><th>Operations\/sec<\/th><\/tr><\/thead><tbody><tr><td>Always lock<\/td><td><strong>78.94<\/strong><\/td><td>126,675<\/td><\/tr><tr><td>Double-checked<\/td><td><strong>11.16<\/strong><\/td><td>895,873<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>You can find benchmark script here : <a href=\"https:\/\/gist.github.com\/birddevelper\/21793b07679680123942130d46bc8bd3\">https:\/\/gist.github.com\/birddevelper\/21793b07679680123942130d46bc8bd3<\/a>. I also added benchmark for using double-checked lock in class __new__ method vs in meta-class __call__ which is itself another long story.<\/p>\n\n\n\n<p>As you can see, double-checked locking is ~<strong>7x faster<\/strong> than always locking! Once the resource is initialized, the first check fails and we bypass the lock entirely. Only the first few threads pay the synchronization cost.<\/p>\n\n\n\n<p>Let&#8217;s check a  Java example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">public class Singleton {\n    private static volatile Singleton instance;\n\n    public static Singleton getInstance() {\n        if (instance == null) {           \/\/ First check: avoid locking after init\n            synchronized (Singleton.class) {\n                if (instance == null) {   \/\/ Second check: only one initializer\n                    instance = new Singleton();\n                }\n            }\n        }\n        return instance;\n    }\n}<\/code><\/pre>\n\n\n\n<p>The second check prevents multiple initialization after threads enter the synchronized block, while <code>synchronized<\/code> and <code>volatile<\/code> provide the memory-visibility and ordering guarantees. When a thread sees a non-null <code>volatile<\/code> reference, it also sees the effects of the constructor that happened-before that write. <\/p>\n\n\n\n<p> Let me know in the comments, I&#8217;d love to hear your experience with this pattern!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the early years of my career, when I first encountered double-checked locking in code, I thought it was a redundant code line checking same &hellip; <\/p>\n","protected":false},"author":1,"featured_media":2609,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,291,69,35,41],"tags":[285,281,370,9,374,376,39,373,372,371,375,282],"_links":{"self":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/2604"}],"collection":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/comments?post=2604"}],"version-history":[{"count":4,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/2604\/revisions"}],"predecessor-version":[{"id":2620,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/2604\/revisions\/2620"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media\/2609"}],"wp:attachment":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media?parent=2604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/categories?post=2604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/tags?post=2604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}