[{"data":1,"prerenderedAt":138},["ShallowReactive",2],{"qa-\u002Fjava\u002Fconcurrency\u002Fexecutors-thread-pools":3},{"page":4,"siblings":118,"blog":135},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":108,"related":109,"seo":110,"seoDescription":111,"stem":112,"subtopic":113,"topic":114,"topicSlug":115,"updated":116,"__hash__":117},"qa\u002Fjava\u002Fconcurrency\u002Fexecutors-thread-pools.md","Executors Thread Pools",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Java","java",{},true,3,"\u002Fjava\u002Fconcurrency\u002Fexecutors-thread-pools",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104],{"id":24,"difficulty":25,"q":26,"a":27},"why-thread-pools","medium","Why use a thread pool instead of creating a new thread per task?","Creating a thread is **expensive** — each one needs a stack (often ~512KB–1MB),\na kernel scheduling entry, and OS bookkeeping. Spawning one per task means\nunbounded thread creation under load, which exhausts memory and thrashes the\nscheduler. A **thread pool** reuses a fixed set of worker threads to run many\ntasks, so you pay the creation cost once and **bound resource usage**.\n\n```java\n\u002F\u002F anti-pattern: a new OS thread per request — no limit, no reuse\nfor (Runnable task : tasks) new Thread(task).start();\n\n\u002F\u002F pooled: a bounded set of workers pulls tasks off a queue\nExecutorService pool = Executors.newFixedThreadPool(8);\nfor (Runnable task : tasks) pool.submit(task);\n```\n\nThe two wins interviewers want: **reuse** (amortize thread cost) and **bounding**\n(a cap on concurrency so a spike can't take down the JVM). The pool also\ndecouples *task submission* from *task execution*.\n",{"id":29,"difficulty":25,"q":30,"a":31},"executor-hierarchy","What is the Executor \u002F ExecutorService \u002F ScheduledExecutorService hierarchy?","They form a layered interface stack, each adding capability:\n\n- **`Executor`** — the minimal contract: a single `execute(Runnable)` method.\n  It just *runs* a task; how (now, pooled, async) is the implementation's choice.\n- **`ExecutorService`** — extends `Executor` with **lifecycle** (`shutdown`,\n  `awaitTermination`) and **result-bearing** submission (`submit`, `invokeAll`,\n  `invokeAny`) that return `Future`s.\n- **`ScheduledExecutorService`** — extends `ExecutorService` to run tasks\n  **after a delay** or **periodically** (`schedule`, `scheduleAtFixedRate`).\n\n```java\nExecutor e = Runnable::run;                       \u002F\u002F simplest possible Executor\nExecutorService es = Executors.newFixedThreadPool(4);\nScheduledExecutorService ses = Executors.newScheduledThreadPool(2);\n```\n\nYou almost always program against **`ExecutorService`** — it gives you both task\nresults and a way to shut the pool down cleanly.\n",{"id":33,"difficulty":25,"q":34,"a":35},"executors-factory-methods","What thread pools do the Executors factory methods create?","`Executors` is a factory of preconfigured `ExecutorService`s:\n\n| Method | Behavior |\n| ------ | -------- |\n| `newFixedThreadPool(n)` | `n` fixed threads, **unbounded** task queue |\n| `newCachedThreadPool()` | grows on demand, idle threads die after 60s, **no queue** (synchronous handoff) |\n| `newSingleThreadExecutor()` | one thread, tasks run **sequentially**, unbounded queue |\n| `newScheduledThreadPool(n)` | `n` threads for delayed\u002Fperiodic tasks |\n| `newWorkStealingPool()` | a `ForkJoinPool` sized to CPUs, work-stealing |\n| `newVirtualThreadPerTaskExecutor()` | a new **virtual thread** per task (Java 21+) |\n\n```java\nExecutorService fixed  = Executors.newFixedThreadPool(8);\nExecutorService cached = Executors.newCachedThreadPool();\n```\n\nEach is just a `ThreadPoolExecutor` (or `ForkJoinPool`) with specific defaults —\nknowing those defaults explains the pitfalls in the next question.\n",{"id":37,"difficulty":14,"q":38,"a":39},"why-executors-discouraged","Why is using Executors.* often discouraged in favor of ThreadPoolExecutor?","The convenience factories hide **dangerous defaults** that can cause\n`OutOfMemoryError` under load:\n\n- `newFixedThreadPool` \u002F `newSingleThreadExecutor` use an **unbounded\n  `LinkedBlockingQueue`** — if tasks arrive faster than they finish, the queue\n  grows without limit until the heap is exhausted.\n- `newCachedThreadPool` has **no upper bound on threads** — a burst can spawn\n  thousands of threads and run the machine out of native memory.\n\n```java\n\u002F\u002F explicit, safe: bounded queue + bounded threads + an explicit reject policy\nExecutorService pool = new ThreadPoolExecutor(\n    8, 16, 60L, TimeUnit.SECONDS,\n    new ArrayBlockingQueue\u003C>(1000),          \u002F\u002F bounded queue\n    new ThreadPoolExecutor.CallerRunsPolicy() \u002F\u002F backpressure on overflow\n);\n```\n\nEffective Java's guidance is to **construct `ThreadPoolExecutor` directly** so\nevery parameter (queue bound, max threads, rejection behavior) is a deliberate\ndecision rather than a hidden default.\n",{"id":41,"difficulty":14,"q":42,"a":43},"threadpoolexecutor-params","What are the core constructor parameters of ThreadPoolExecutor?","`ThreadPoolExecutor` has six knobs that fully define its behavior:\n\n| Parameter | Meaning |\n| --------- | ------- |\n| `corePoolSize` | threads kept alive even when idle |\n| `maximumPoolSize` | hard cap on total threads |\n| `keepAliveTime` | how long **non-core** idle threads survive before dying |\n| `workQueue` | the `BlockingQueue` that holds waiting tasks |\n| `threadFactory` | creates worker threads (name them, set daemon\u002Fpriority) |\n| `handler` | `RejectedExecutionHandler` for when the pool is saturated |\n\n```java\nnew ThreadPoolExecutor(\n    4,                                  \u002F\u002F corePoolSize\n    10,                                 \u002F\u002F maximumPoolSize\n    30L, TimeUnit.SECONDS,              \u002F\u002F keepAliveTime\n    new ArrayBlockingQueue\u003C>(100),      \u002F\u002F workQueue\n    new CustomThreadFactory(\"worker\"),  \u002F\u002F threadFactory\n    new ThreadPoolExecutor.AbortPolicy()\u002F\u002F handler\n);\n```\n\nAlways supply a **named `ThreadFactory`** in production — default thread names\nlike `pool-1-thread-3` make stack traces and thread dumps nearly useless.\n",{"id":45,"difficulty":14,"q":46,"a":47},"task-flow","How does a task flow through core pool, queue and max pool?","`ThreadPoolExecutor` applies a strict, sometimes surprising, order on `execute`:\n\n1. If running threads **\u003C `corePoolSize`**, start a **new core thread** for the\n   task (even if other threads are idle).\n2. Else, try to **enqueue** the task in the `workQueue`.\n3. Only if the **queue is full** does it create threads up to `maximumPoolSize`.\n4. If the queue is full **and** threads are at max, the task is **rejected**.\n\n```java\n\u002F\u002F core=2, queue=2, max=4 -> capacity surfaces in this order:\n\u002F\u002F tasks 1-2  -> run on 2 core threads\n\u002F\u002F tasks 3-4  -> wait in the queue (size 2)\n\u002F\u002F tasks 5-6  -> spawn 2 extra threads (up to max=4)\n\u002F\u002F task  7    -> rejected\n```\n\nThe non-obvious consequence: with an **unbounded queue**, step 3 never triggers,\nso `maximumPoolSize` is **ignored** and the pool never grows past core. This is\nexactly why `newFixedThreadPool` only ever runs `n` threads.\n",{"id":49,"difficulty":14,"q":50,"a":51},"rejection-policies","What are the four rejection policies and when is a task rejected?","A task is **rejected** when the queue is full *and* the pool is at\n`maximumPoolSize` (or after shutdown). The `RejectedExecutionHandler` decides\nwhat happens:\n\n| Policy | Behavior |\n| ------ | -------- |\n| `AbortPolicy` (default) | throws `RejectedExecutionException` |\n| `CallerRunsPolicy` | runs the task on the **submitting thread** (natural backpressure) |\n| `DiscardPolicy` | silently drops the task |\n| `DiscardOldestPolicy` | drops the **oldest queued** task, retries the new one |\n\n```java\nvar pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,\n    new ArrayBlockingQueue\u003C>(10),\n    new ThreadPoolExecutor.CallerRunsPolicy());\n```\n\n`CallerRunsPolicy` is the favorite for throughput-with-stability: when the pool\nis overwhelmed, the producer is *slowed down* (it executes the task itself)\ninstead of either throwing or losing work. You can also implement your own\nhandler to log, meter, or persist rejected tasks.\n",{"id":53,"difficulty":14,"q":54,"a":55},"sizing-pools","How do you size a thread pool for CPU-bound vs IO-bound work?","The right size depends on what threads spend their time doing:\n\n- **CPU-bound** work keeps the core busy, so more threads than cores just adds\n  context-switching overhead. Size ≈ **number of cores** (sometimes `cores + 1`\n  to cover the occasional page fault).\n- **IO-bound** work blocks on network\u002Fdisk, leaving the CPU idle, so you want\n  **many more threads than cores** to keep the CPU saturated.\n\nA useful formula (Brian Goetz): `threads = cores × (1 + waitTime \u002F computeTime)`.\n\n```java\nint cores = Runtime.getRuntime().availableProcessors();\nExecutorService cpuPool = Executors.newFixedThreadPool(cores);     \u002F\u002F CPU-bound\nExecutorService ioPool  = Executors.newFixedThreadPool(cores * 8); \u002F\u002F IO-bound (illustrative)\n```\n\nTreat the formula as a **starting point** — measure throughput and latency under\nrealistic load and tune. (For heavily IO-bound work on Java 21+, **virtual\nthreads** sidestep sizing entirely.)\n",{"id":57,"difficulty":25,"q":58,"a":59},"submit-vs-execute","What is the difference between submit() and execute()?","`execute(Runnable)` comes from `Executor` and returns **`void`** — it's\nfire-and-forget. `submit(...)` comes from `ExecutorService`, accepts a\n`Runnable` *or* `Callable`, and returns a **`Future`** you can use to get the\nresult, wait for completion, or cancel.\n\n```java\npool.execute(() -> log(\"done\"));        \u002F\u002F void, no handle\n\nFuture\u003CInteger> f = pool.submit(() -> 42); \u002F\u002F Future handle\nInteger result = f.get();                  \u002F\u002F 42\n```\n\nA subtle but important difference for debugging: with `execute`, an uncaught\nexception propagates to the thread's **`UncaughtExceptionHandler`** (you see it).\nWith `submit`, the exception is **captured inside the `Future`** and only\nsurfaces when you call `get()` — so a `submit`ted task that throws can fail\n**silently** if you never inspect the `Future`.\n",{"id":61,"difficulty":25,"q":62,"a":63},"runnable-vs-callable","What is the difference between Runnable and Callable?","Both represent a unit of work, but:\n\n- **`Runnable`** — `void run()`, **cannot return a value**, and **cannot throw\n  checked exceptions**.\n- **`Callable\u003CV>`** — `V call() throws Exception`, **returns a result** and **may\n  throw checked exceptions**.\n\n```java\nRunnable r = () -> System.out.println(\"side effect only\");\nCallable\u003CInteger> c = () -> {\n    if (bad) throw new IOException(\"checked exception is fine here\");\n    return compute();                 \u002F\u002F returns a value\n};\nFuture\u003CInteger> f = pool.submit(c);\n```\n\nUse `Callable` when the task **produces a result** or might throw a checked\nexception. You can adapt a `Runnable` to a `Callable` via\n`Executors.callable(runnable)`.\n",{"id":65,"difficulty":25,"q":66,"a":67},"future","What is a Future and what can you do with it?","A `Future\u003CV>` is a **handle to a result that may not exist yet** — the receipt\nyou get back from `submit`. Its key methods:\n\n- `get()` — **blocks** until the task completes and returns the result (or the\n  timed `get(timeout, unit)` variant).\n- `isDone()` — non-blocking check for completion.\n- `cancel(mayInterruptIfRunning)` — attempts to cancel; `isCancelled()` reports it.\n\n```java\nFuture\u003CInteger> f = pool.submit(() -> slowComputation());\nif (!f.isDone()) doOtherWork();\ntry {\n    Integer v = f.get(2, TimeUnit.SECONDS); \u002F\u002F wait up to 2s\n} catch (TimeoutException te) {\n    f.cancel(true);                          \u002F\u002F give up, interrupt the task\n}\n```\n\nThe classic limitation: a plain `Future` has **no callbacks** — you can only\n*poll* `isDone()` or *block* on `get()`. That gap is what `CompletableFuture`\nfills.\n",{"id":69,"difficulty":14,"q":70,"a":71},"future-exceptions","How are exceptions from a submitted task surfaced?","If a task throws, the exception is **stored in the `Future`** and re-thrown,\n**wrapped in an `ExecutionException`**, when you call `get()`. The original cause\nis available via `getCause()`.\n\n```java\nFuture\u003CInteger> f = pool.submit(() -> { throw new IllegalStateException(\"boom\"); });\ntry {\n    f.get();\n} catch (ExecutionException e) {\n    Throwable cause = e.getCause();   \u002F\u002F the original IllegalStateException\n} catch (InterruptedException e) {\n    Thread.currentThread().interrupt(); \u002F\u002F restore the interrupt flag\n}\n```\n\nTwo interview points: `get()` declares both **`ExecutionException`** (task threw)\nand **`InterruptedException`** (the waiting thread was interrupted), and you must\nunwrap `getCause()` to see what actually went wrong. A task whose result you\nnever `get()` can swallow its failure entirely.\n",{"id":73,"difficulty":25,"q":74,"a":75},"invokeall-invokeany","What do invokeAll and invokeAny do?","Both submit a **collection of `Callable`s** at once and block:\n\n- **`invokeAll`** runs **all** tasks and returns a `List\u003CFuture>` once **every**\n  task has finished (each `Future` is already complete).\n- **`invokeAny`** returns the result of the **first** task to complete\n  successfully, then **cancels the rest** — great for racing redundant lookups.\n\n```java\nList\u003CCallable\u003CInteger>> tasks = List.of(() -> 1, () -> 2, () -> 3);\n\nList\u003CFuture\u003CInteger>> all = pool.invokeAll(tasks); \u002F\u002F waits for all 3\nfor (Future\u003CInteger> f : all) use(f.get());\n\nInteger fastest = pool.invokeAny(tasks); \u002F\u002F first success, others cancelled\n```\n\nBoth have **timeout overloads**. With `invokeAll`, any task that fails surfaces\nonly when you call `get()` on its `Future`; `invokeAny` throws\n`ExecutionException` only if **every** task fails.\n",{"id":77,"difficulty":14,"q":78,"a":79},"completablefuture-intro","What problem does CompletableFuture solve over Future?","A plain `Future` can only be **polled or blocked on** — you can't attach a\ncontinuation or compose multiple async results without blocking a thread.\n`CompletableFuture` (Java 8+) implements `CompletionStage`, adding a fluent,\n**non-blocking, callback-driven** pipeline.\n\n```java\nCompletableFuture\n    .supplyAsync(() -> fetchUser(id))       \u002F\u002F run async, returns a stage\n    .thenApply(User::name)                  \u002F\u002F transform the result\n    .thenAccept(System.out::println)        \u002F\u002F consume it, no blocking\n    .exceptionally(ex -> { log(ex); return null; }); \u002F\u002F handle failure\n```\n\nYou can **chain** transformations, **combine** independent futures, and **handle\nerrors** declaratively — turning callback spaghetti into a readable flow.\n`supplyAsync`\u002F`runAsync` use the **common ForkJoinPool** by default; pass your own\n`Executor` for control over which pool runs the work.\n",{"id":81,"difficulty":14,"q":82,"a":83},"completablefuture-chaining","What is the difference between thenApply, thenCompose and thenCombine?","They cover the three common composition shapes:\n\n- **`thenApply(fn)`** — transform the result with a **plain function**\n  (`T -> U`). Result is `CompletableFuture\u003CU>`.\n- **`thenCompose(fn)`** — chain a function that **itself returns a future**\n  (`T -> CompletableFuture\u003CU>`); it **flattens**, avoiding a nested\n  `CompletableFuture\u003CCompletableFuture\u003CU>>`. This is the async \"flatMap\".\n- **`thenCombine(other, bifn)`** — wait for **two independent** futures and\n  **merge** their results.\n\n```java\ncf.thenApply(x -> x + 1);                       \u002F\u002F sync transform\ncf.thenCompose(id -> fetchAsync(id));           \u002F\u002F chain dependent async call\ncf1.thenCombine(cf2, (a, b) -> a + b);          \u002F\u002F join two parallel results\n```\n\nRule of thumb: use **`thenApply`** for a sync mapping, **`thenCompose`** when the\nmapping is *itself* async, and **`thenCombine`** to fan two parallel results back\ntogether.\n",{"id":85,"difficulty":25,"q":86,"a":87},"completablefuture-errors","How do you handle errors in a CompletableFuture pipeline?","Three methods catch failures that propagate down the chain:\n\n- **`exceptionally(fn)`** — runs **only on failure**, supplying a fallback value\n  (`Throwable -> T`).\n- **`handle(bifn)`** — runs on **both** success and failure\n  (`(T result, Throwable ex) -> U`), so you can recover or transform either way.\n- **`whenComplete(bifn)`** — a **side-effect** callback on completion that does\n  **not** alter the result (good for logging\u002Fcleanup).\n\n```java\ncf.thenApply(this::risky)\n  .exceptionally(ex -> DEFAULT)              \u002F\u002F fallback on error only\n  .handle((res, ex) -> ex != null ? -1 : res) \u002F\u002F see both outcomes\n  .whenComplete((res, ex) -> log(res, ex));   \u002F\u002F observe, don't change\n```\n\nNote that exceptions arrive **wrapped in `CompletionException`** (use\n`getCause()`), and `exceptionally` recovers the chain so later stages see the\nfallback value rather than the error.\n",{"id":89,"difficulty":25,"q":90,"a":91},"completablefuture-allof-anyof","What do CompletableFuture.allOf and anyOf do?","They aggregate **multiple** futures:\n\n- **`allOf(cf...)`** — completes when **all** given futures complete. It returns\n  `CompletableFuture\u003CVoid>`, so you join, then read each future's result\n  individually.\n- **`anyOf(cf...)`** — completes when the **first** of them completes, carrying\n  that future's result (as `Object`).\n\n```java\nCompletableFuture\u003CString> a = supplyAsync(() -> callA());\nCompletableFuture\u003CString> b = supplyAsync(() -> callB());\n\nCompletableFuture.allOf(a, b).join();        \u002F\u002F wait for both\nString combined = a.join() + b.join();       \u002F\u002F now safe, both done\n\nObject first = CompletableFuture.anyOf(a, b).join(); \u002F\u002F first to finish\n```\n\nBecause `allOf` yields `Void`, the idiom is to `join()` it as a barrier and then\npull each individual result — frequently done by collecting a list of futures\nand `allOf`-ing the array.\n",{"id":93,"difficulty":14,"q":94,"a":95},"shutdown-vs-shutdownnow","What is the difference between shutdown, shutdownNow and awaitTermination?","An `ExecutorService` keeps its threads alive (often non-daemon) until you stop\nit, so you **must** shut it down or the JVM may never exit:\n\n- **`shutdown()`** — graceful: stops accepting new tasks but **lets already\n  submitted tasks finish**. Returns immediately.\n- **`shutdownNow()`** — aggressive: **interrupts** running tasks, **drains** the\n  queue, and returns the list of tasks that never started.\n- **`awaitTermination(timeout, unit)`** — **blocks** until the pool has\n  terminated or the timeout elapses; returns `true` if it terminated.\n\n```java\npool.shutdown();\nif (!pool.awaitTermination(30, TimeUnit.SECONDS)) {\n    pool.shutdownNow(); \u002F\u002F force the stragglers\n}\n```\n\nNeither `shutdown` method blocks on its own — `awaitTermination` is how you\nactually *wait*. And `shutdownNow` only **requests** interruption; tasks that\nignore the interrupt flag keep running.\n",{"id":97,"difficulty":14,"q":98,"a":99},"graceful-shutdown-pattern","What is the recommended graceful shutdown pattern for an executor?","The canonical two-phase pattern (straight from the `ExecutorService` Javadoc):\nask politely, wait, then force, and **restore the interrupt** if you're\ninterrupted while waiting.\n\n```java\nvoid shutdownAndAwait(ExecutorService pool) {\n    pool.shutdown();                       \u002F\u002F stop taking new tasks\n    try {\n        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {\n            pool.shutdownNow();            \u002F\u002F cancel in-flight tasks\n            if (!pool.awaitTermination(60, TimeUnit.SECONDS))\n                log(\"pool did not terminate\");\n        }\n    } catch (InterruptedException ie) {\n        pool.shutdownNow();\n        Thread.currentThread().interrupt(); \u002F\u002F preserve interrupt status\n    }\n}\n```\n\nWire this into a JVM shutdown hook or your framework's lifecycle. The key habits:\n**two await phases**, escalate from `shutdown` to `shutdownNow`, and never swallow\n`InterruptedException` — re-set the flag.\n",{"id":101,"difficulty":25,"q":102,"a":103},"scheduledexecutorservice","How does ScheduledExecutorService schedule periodic tasks?","It runs tasks after a delay or repeatedly, replacing the legacy `Timer`\u002F\n`TimerTask` (which used a single thread and let one failing task kill all others):\n\n- **`schedule(task, delay, unit)`** — run **once** after a delay.\n- **`scheduleAtFixedRate(task, initial, period, unit)`** — start each run every\n  `period` **from the start of the previous run** (fixed cadence).\n- **`scheduleWithFixedDelay(task, initial, delay, unit)`** — wait `delay` **after\n  each run finishes** before the next starts.\n\n```java\nvar ses = Executors.newScheduledThreadPool(2);\nses.scheduleAtFixedRate(this::poll, 0, 5, TimeUnit.SECONDS);   \u002F\u002F every 5s\nses.scheduleWithFixedDelay(this::cleanup, 1, 10, TimeUnit.SECONDS);\n```\n\nThe crucial difference: **fixed-rate** can let runs bunch up (or overlap\nconceptually) if a task runs longer than the period, while **fixed-delay**\nguarantees a gap between runs. Also note an **uncaught exception silently\ncancels** all future executions of that scheduled task.\n",{"id":105,"difficulty":14,"q":106,"a":107},"virtual-threads","How do virtual threads change thread-pool thinking in Java 21?","**Virtual threads** (Java 21, JEP 444) are lightweight threads managed by the JVM,\nnot 1:1 with OS threads. They're so cheap (a few hundred bytes) that you can have\n**millions**, and a blocking call **unmounts** the virtual thread from its\ncarrier OS thread instead of blocking it.\n\n```java\n\u002F\u002F one virtual thread per task — no pooling, no sizing\ntry (var executor = Executors.newVirtualThreadPerTaskExecutor()) {\n    for (var task : tasks) executor.submit(task);\n} \u002F\u002F try-with-resources auto-closes (shutdown + await)\n```\n\nThis upends the classic advice: for **IO-bound** work you no longer **pool**\nvirtual threads or agonize over pool sizing — you just create one per task.\nCaveats: **don't pool** them, avoid pinning (long `synchronized` blocks or native\ncalls keep the carrier blocked), and **platform-thread pools still matter for\nCPU-bound** work, where bounding parallelism to the core count is still right.\n",21,null,{"description":11},"Java executors and thread pool interview questions — the Executor framework, ExecutorService, ThreadPoolExecutor tuning, Callable and Future, CompletableFuture, and graceful shutdown.","java\u002Fconcurrency\u002Fexecutors-thread-pools","Executors & Thread Pools","Concurrency","concurrency","2026-06-20","cC2QXAGnVLuTnndEQtQ1cWXq-_dNO9YBs8R26jGFmiw",[119,123,126,127,131],{"subtopic":120,"path":121,"order":122},"Threads & Synchronization","\u002Fjava\u002Fconcurrency\u002Fthreads",1,{"subtopic":124,"path":125,"order":12},"Synchronization & Locks","\u002Fjava\u002Fconcurrency\u002Fsynchronization-locks",{"subtopic":113,"path":21,"order":20},{"subtopic":128,"path":129,"order":130},"Concurrent Collections","\u002Fjava\u002Fconcurrency\u002Fconcurrent-collections",4,{"subtopic":132,"path":133,"order":134},"volatile & Memory Model","\u002Fjava\u002Fconcurrency\u002Fvolatile-memory-model",5,{"path":136,"title":137},"\u002Fblog\u002Fjava-executors-thread-pools","Java Executors, Thread Pools & CompletableFuture — A Complete Guide",1782244117343]