{"id":35012285,"url":"https://github.com/forax/how_to_stop_a_thread","last_synced_at":"2025-12-27T05:00:49.326Z","repository":{"id":244367786,"uuid":"815040843","full_name":"forax/how_to_stop_a_thread","owner":"forax","description":"Different ways in Java to stop a thread","archived":false,"fork":false,"pushed_at":"2024-07-31T14:35:46.000Z","size":20,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-07-31T18:02:05.372Z","etag":null,"topics":["concurrency","java","performance","thread","volatile"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-14T08:17:58.000Z","updated_at":"2024-07-31T14:35:50.000Z","dependencies_parsed_at":"2024-06-14T09:47:25.876Z","dependency_job_id":"4c5235ae-26ed-46d4-b8ad-56e54044a182","html_url":"https://github.com/forax/how_to_stop_a_thread","commit_stats":null,"previous_names":["forax/how_to_stop_a_thread"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/forax/how_to_stop_a_thread","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forax%2Fhow_to_stop_a_thread","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forax%2Fhow_to_stop_a_thread/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forax%2Fhow_to_stop_a_thread/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forax%2Fhow_to_stop_a_thread/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forax","download_url":"https://codeload.github.com/forax/how_to_stop_a_thread/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forax%2Fhow_to_stop_a_thread/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28072676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-27T02:00:05.897Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","java","performance","thread","volatile"],"created_at":"2025-12-27T05:00:15.654Z","updated_at":"2025-12-27T05:00:49.293Z","avatar_url":"https://github.com/forax.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# how_to_stop_a_thread\nA study of the different ways to stop a thread in Java.\n\nFor each way to stop a thread, we bench the cost of reading the value `stop` value once\nor multiple times in a loop of an array of 100 000 elements.\n\nThe following results are on my MacBook Air M2:\n```\n// ThreadStopBench.stop_synchronized    avgt    5  5.055 ± 0.023  ns/op\n// ThreadStopBench.stop_reentrant_lock  avgt    5  8.406 ± 0.035  ns/op\n// ThreadStopBench.stop_interrupt       avgt    5  0.490 ± 0.002  ns/op\n// ThreadStopBench.stop_volatile        avgt    5  0.496 ± 0.004  ns/op\n// ThreadStopBench.stop_opaque          avgt    5  0.409 ± 0.004  ns/op\n// ThreadStopBench.stop_callsite        avgt    5  0.306 ± 0.002  ns/op\n// ThreadStopBench.stop_arena           avgt    5  0.613 ± 0.004  ns/op\n```\n\n```\n// ThreadStopLoopBench.stop_synchronized    avgt    5  510.287 ± 24.235  us/op\n// ThreadStopLoopBench.stop_reentrant_lock  avgt    5  833.998 ±  1.591  us/op\n// ThreadStopLoopBench.stop_interrupt       avgt    5   51.382 ±  0.423  us/op\n// ThreadStopLoopBench.stop_volatile        avgt    5   51.867 ±  0.303  us/op\n// ThreadStopLoopBench.stop_opaque          avgt    5   30.584 ±  0.075  us/op\n// ThreadStopLoopBench.stop_callsite        avgt    5   30.604 ±  0.071  us/op\n// ThreadStopLoopBench.stop_arena           avgt    5   30.592 ±  0.091  us/op\n```\n\nThe following results are on an x86_64 Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz\n```\nBenchmark                                Mode  Cnt     Score    Error  Units\nThreadStopBench.stop_synchronized        avgt    5    16.696 ±  0.745  ns/op\nThreadStopBench.stop_reentrant_lock      avgt    5    13.898 ±  0.085  ns/op\nThreadStopBench.stop_interrupt           avgt    5     0.524 ±  0.006  ns/op\nThreadStopBench.stop_volatile            avgt    5     0.522 ±  0.023  ns/op\nThreadStopBench.stop_opaque              avgt    5     0.522 ±  0.023  ns/op\nThreadStopBench.stop_callsite            avgt    5     0.393 ±  0.001  ns/op\nThreadStopBench.stop_arena               avgt    5     0.785 ±  0.021  ns/op\n```\n\n```\nThreadStopLoopBench.stop_synchronized    avgt    5  1678.665 ± 38.996  us/op\nThreadStopLoopBench.stop_reentrant_lock  avgt    5  1409.330 ± 47.300  us/op\nThreadStopLoopBench.stop_interrupt       avgt    5    65.532 ±  0.358  us/op\nThreadStopLoopBench.stop_volatile        avgt    5    65.549 ±  0.832  us/op\nThreadStopLoopBench.stop_opaque          avgt    5    27.700 ±  1.323  us/op\nThreadStopLoopBench.stop_callsite        avgt    5    27.353 ±  0.050  us/op\nThreadStopLoopBench.stop_arena           avgt    5    27.844 ±  0.344  us/op\n```\n\n1. Using synchronized\n\nHere, we are using the keyword synchronized to protect the access to the field `stop`.\n\n```java\nboolean stop;\nfinal Object lock = new Object();\n\nvoid loop() {\n  while(true) {\n    synchronized (lock) {\n      if (stop) {\n        break;\n      }\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  synchronized (lock) {\n    stop = true;\n  }\n}\n```\n\n2. Using ReentrantLock\n\nSame code but using a reentrant lock instead of a synchronized block.\n\n```java\nboolean stop;\nfinal ReentrantLock lock = new ReentrantLock();\n\nvoid loop() {\n  while(true) {\n    lock.lock();\n    try {\n      if (stop) {\n        break;\n      }\n    } finally {\n      lock.unlock();\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  lock.lock();\n  try {\n    stop = true;\n  } finally {\n    lock.unlock();\n  }\n}\n```\n\n3. Using interrupted\n\nJava has its own mechanism to interrupt a thread.\n\n```java\nvoid loop() {\n  while(true) {\n    if (Thread.interrupted()) {\n      break;\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  thread.interrupt();\n}\n```\n\n4. Using volatile\n\nThread.interrupted()/interrupt are using internally a volatile field.\n\n```java\nvolatile boolean stop;\n\nvoid loop() {\n  while(true) {\n    if (stop) {\n      break;\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  stop = true;\n}\n```\n\n5. Using opaque (VarHandle)\n\nInstead of using the volatile semantics, we can use the `opaque` semantics.\nThis is usually faster that using the keyword `volatile` but sadly, this is usually\nnot the semantics we want, `opaque` does not guarantee that the preview writes will be seen\nby the thread that reads the values when stop becomes true\n\n```java\nstatic final VarHandle STOP = createVH();\n\nprivate static VarHandle createVH() {\n  var lookup = MethodHandles.lookup();\n  try {\n    return lookup.findVarHandle(lookup.lookupClass(), \"stop\", boolean.class);\n  } catch (NoSuchFieldException | IllegalAccessException e) {\n    throw new AssertionError(e);\n  }\n}\n\nboolean stop;\n\nvoid loop() {\n  while(true) {\n    var stop = (boolean) STOP.getOpaque(this);\n    if (stop) {\n      break;\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  STOP.setOpaque(this, true);\n}\n```\n\n6. using a MutableCallSite\n\nWe can cheat and say that because there only one thread we can use a global state\nand then uses a MutableCallSite to first always return `false` and then always return `true`.\nInternally the VM will first generate a code that skip the branch of the `if` because `code`\nis always `true`and then when de-optimize the code when the code is changed to return `true`.\n\n```java\nstatic final class Stop extends MutableCallSite {\n  public Stop() {\n    super(MethodType.methodType(boolean.class));\n    setTarget(MethodHandles.constant(boolean.class, false));\n  }\n}\n\nstatic final Stop STOP = new Stop();\nstatic final MethodHandle STOP_MH = STOP.dynamicInvoker();\n\nvoid loop() {\n  while(true) {\n    boolean stop;\n    try {\n      stop = (boolean) STOP_MH.invokeExact();\n    } catch (RuntimeException | Error e) {\n      throw e;\n    } catch (Throwable e) {\n      throw new AssertionError(e);\n    }\n    if (stop) {\n      break;\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  STOP.setTarget(MethodHandles.constant(boolean.class, true));\n  MutableCallSite.syncAll(new MutableCallSite[] { STOP });\n}\n```\n\n7. Using foreign memory Arena\n\nWhen the `Arena` created using `ofAshared()` is closed, it forces all the other threads to go to a GC safepoint,\nso the cost of calling `scope.isAlive()` is a simple read.\n\n```java\nfinal Arena arena = Arena.ofShared();\nfinal MemorySegment.Scope scope = arena.scope();\n\nvoid loop() {\n  while(true) {\n    if (!scope.isAlive()) {\n      break;\n    }\n    // ...\n  }\n  System.out.println(\"end !\");\n}\n\nvoid main() throws InterruptedException {\n  var thread = new Thread(this::loop);\n  thread.start();\n\n  Thread.sleep(1_000);\n  arena.close();\n}\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforax%2Fhow_to_stop_a_thread","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforax%2Fhow_to_stop_a_thread","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforax%2Fhow_to_stop_a_thread/lists"}