diff --git a/CHANGES.md b/CHANGES.md
index 4f4c6a2..dc52ca3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -20,6 +20,8 @@
 - Tuple unpacking on `return` and `yield` constructs now implies 3.8+ (#2700)
 - Unparenthesized tuples on annotated assignments (e.g
   `values: Tuple[int, ...] = 1, 2, 3`) now implies 3.8+ (#2708)
+- Allow setting custom cache directory on all platforms with environment variable
+  `BLACK_CACHE_DIR` (#2739).
 - Text coloring added in the final statistics (#2712)
 - For stubs, one blank line between class attributes and methods is now kept if there's
   at least one pre-existing blank line (#2736)
diff --git a/docs/contributing/reference/reference_functions.rst b/docs/contributing/reference/reference_functions.rst
index 4353d1b..01ffe44 100644
--- a/docs/contributing/reference/reference_functions.rst
+++ b/docs/contributing/reference/reference_functions.rst
@@ -96,6 +96,8 @@ Caching
 
 .. autofunction:: black.cache.filter_cached
 
+.. autofunction:: black.cache.get_cache_dir
+
 .. autofunction:: black.cache.get_cache_file
 
 .. autofunction:: black.cache.get_cache_info
diff --git a/docs/usage_and_configuration/file_collection_and_discovery.md b/docs/usage_and_configuration/file_collection_and_discovery.md
index 1f43618..bd90ccc 100644
--- a/docs/usage_and_configuration/file_collection_and_discovery.md
+++ b/docs/usage_and_configuration/file_collection_and_discovery.md
@@ -22,10 +22,12 @@ run. The file is non-portable. The standard location on common operating systems
 `file-mode` is an int flag that determines whether the file was formatted as 3.6+ only,
 as .pyi, and whether string normalization was omitted.
 
-To override the location of these files on macOS or Linux, set the environment variable
-`XDG_CACHE_HOME` to your preferred location. For example, if you want to put the cache
-in the directory you're running _Black_ from, set `XDG_CACHE_HOME=.cache`. _Black_ will
-then write the above files to `.cache/black/<version>/`.
+To override the location of these files on all systems, set the environment variable
+`BLACK_CACHE_DIR` to the preferred location. Alternatively on macOS and Linux, set
+`XDG_CACHE_HOME` to you your preferred location. For example, if you want to put the
+cache in the directory you're running _Black_ from, set `BLACK_CACHE_DIR=.cache/black`.
+_Black_ will then write the above files to `.cache/black`. Note that `BLACK_CACHE_DIR`
+will take precedence over `XDG_CACHE_HOME` if both are set.
 
 ## .gitignore
 
diff --git a/src/black/cache.py b/src/black/cache.py
index bca7279..552c248 100644
--- a/src/black/cache.py
+++ b/src/black/cache.py
@@ -20,7 +20,23 @@ CacheInfo = Tuple[Timestamp, FileSize]
 Cache = Dict[str, CacheInfo]
 
 
-CACHE_DIR = Path(user_cache_dir("black", version=__version__))
+def get_cache_dir() -> Path:
+    """Get the cache directory used by black.
+
+    Users can customize this directory on all systems using `BLACK_CACHE_DIR`
+    environment variable. By default, the cache directory is the user cache directory
+    under the black application.
+
+    This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid
+    repeated calls.
+    """
+    # NOTE: Function mostly exists as a clean way to test getting the cache directory.
+    default_cache_dir = user_cache_dir("black", version=__version__)
+    cache_dir = Path(os.environ.get("BLACK_CACHE_DIR", default_cache_dir))
+    return cache_dir
+
+
+CACHE_DIR = get_cache_dir()
 
 
 def read_cache(mode: Mode) -> Cache:

diff --git a/tests/test_black.py b/tests/test_black.py
index fd01425..5596909 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -40,7 +40,7 @@ import black
 import black.files
 from black import Feature, TargetVersion
 from black import re_compile_maybe_verbose as compile_pattern
-from black.cache import get_cache_file
+from black.cache import get_cache_dir, get_cache_file
 from black.debug import DebugVisitor
 from black.output import color_diff, diff
 from black.report import Report
@@ -1601,6 +1601,33 @@ class BlackTestCase(BlackBaseTestCase):
 
 
 class TestCaching:
+    def test_get_cache_dir(
+        self,
+        tmp_path: Path,
+        monkeypatch: pytest.MonkeyPatch,
+    ) -> None:
+        # Create multiple cache directories
+        workspace1 = tmp_path / "ws1"
+        workspace1.mkdir()
+        workspace2 = tmp_path / "ws2"
+        workspace2.mkdir()
+
+        # Force user_cache_dir to use the temporary directory for easier assertions
+        patch_user_cache_dir = patch(
+            target="black.cache.user_cache_dir",
+            autospec=True,
+            return_value=str(workspace1),
+        )
+
+        # If BLACK_CACHE_DIR is not set, use user_cache_dir
+        monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
+        with patch_user_cache_dir:
+            assert get_cache_dir() == workspace1
+
+        # If it is set, use the path provided in the env var.
+        monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
+        assert get_cache_dir() == workspace2
+
     def test_cache_broken_file(self) -> None:
         mode = DEFAULT_MODE
         with cache_dir() as workspace:

Custom cache directory to be thread/process safe
**Is your feature request related to a problem? Please describe.**

The cache files are not thread/process safe. I've checked multiple projects simultaneously in different processes but get permission errors when one process is writing to the cache while another is reading from it. On linux the fix is pretty easy: set a different temp directory for each process using the ``XDG_CACHE_HOME`` environment variable but there is no equivalent for Windows (there's somewhat an equivalent but you need a rather [specific setup](https://github.com/platformdirs/platformdirs/blob/main/src/platformdirs/windows.py#L157-L165) to use the environment variable).

**Describe the solution you'd like**

The solution that will work for my use case (and I think is the least amount of changes) is to create a custom environment variable that overrides using platformdirs to get the cache directory. Think this could look like:
```python
CACHE_DIR = Path(os.environ.get("BLACK_CACHE_DIR", user_cache_dir("black", version=__version__)))
if not CACHE_DIR.exists():
    raise RuntimeError(f"{CACHE_DIR} does not exist")
```

**Describe alternatives you've considered**

1. Add the command line option to turn off cacheing (as this ticket as #248 asked for) (guess is this  more work)
2. Add command line option to set the cache directory (guess is this is more work but not a lot)
3. Make the cache dir thread/process safe (guess is this is more work)
tests/test_black.py::BlackTestCase::test_assertFormatEqual tests/test_black.py::BlackTestCase::test_assert_equivalent_different_asts tests/test_black.py::BlackTestCase::test_async_as_identifier tests/test_black.py::BlackTestCase::test_bpo_2142_workaround tests/test_black.py::BlackTestCase::test_bpo_33660_workaround tests/test_black.py::BlackTestCase::test_broken_symlink tests/test_black.py::BlackTestCase::test_check_diff_use_together tests/test_black.py::BlackTestCase::test_code_option tests/test_black.py::BlackTestCase::test_code_option_changed tests/test_black.py::BlackTestCase::test_code_option_check tests/test_black.py::BlackTestCase::test_code_option_check_changed tests/test_black.py::BlackTestCase::test_code_option_color_diff tests/test_black.py::BlackTestCase::test_code_option_config tests/test_black.py::BlackTestCase::test_code_option_diff tests/test_black.py::BlackTestCase::test_code_option_fast tests/test_black.py::BlackTestCase::test_code_option_parent_config tests/test_black.py::BlackTestCase::test_code_option_safe tests/test_black.py::BlackTestCase::test_debug_visitor tests/test_black.py::BlackTestCase::test_detect_pos_only_arguments tests/test_black.py::BlackTestCase::test_empty_ff tests/test_black.py::BlackTestCase::test_endmarker tests/test_black.py::BlackTestCase::test_equivalency_ast_parse_failure_includes_error tests/test_black.py::BlackTestCase::test_experimental_string_processing_warns tests/test_black.py::BlackTestCase::test_expression_diff tests/test_black.py::BlackTestCase::test_expression_diff_with_color tests/test_black.py::BlackTestCase::test_expression_ff tests/test_black.py::BlackTestCase::test_find_project_root tests/test_black.py::BlackTestCase::test_find_user_pyproject_toml_linux tests/test_black.py::BlackTestCase::test_find_user_pyproject_toml_windows tests/test_black.py::BlackTestCase::test_for_handled_unexpected_eof_error tests/test_black.py::BlackTestCase::test_format_file_contents tests/test_black.py::BlackTestCase::test_get_features_used tests/test_black.py::BlackTestCase::test_get_features_used_decorator tests/test_black.py::BlackTestCase::test_get_features_used_for_future_flags tests/test_black.py::BlackTestCase::test_get_future_imports tests/test_black.py::BlackTestCase::test_invalid_cli_regex tests/test_black.py::BlackTestCase::test_invalid_config_return_code tests/test_black.py::BlackTestCase::test_lib2to3_parse tests/test_black.py::BlackTestCase::test_multi_file_force_py36 tests/test_black.py::BlackTestCase::test_multi_file_force_pyi tests/test_black.py::BlackTestCase::test_newline_comment_interaction tests/test_black.py::BlackTestCase::test_no_files tests/test_black.py::BlackTestCase::test_parse_pyproject_toml tests/test_black.py::BlackTestCase::test_pep_572_version_detection tests/test_black.py::BlackTestCase::test_pipe_force_py36 tests/test_black.py::BlackTestCase::test_pipe_force_pyi tests/test_black.py::BlackTestCase::test_piping tests/test_black.py::BlackTestCase::test_piping_diff tests/test_black.py::BlackTestCase::test_piping_diff_with_color tests/test_black.py::BlackTestCase::test_preserves_line_endings tests/test_black.py::BlackTestCase::test_preserves_line_endings_via_stdin tests/test_black.py::BlackTestCase::test_python37 tests/test_black.py::BlackTestCase::test_read_pyproject_toml tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin_and_existing_path tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin_empty tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin_filename tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin_filename_ipynb tests/test_black.py::BlackTestCase::test_reformat_one_with_stdin_filename_pyi tests/test_black.py::BlackTestCase::test_report_normal tests/test_black.py::BlackTestCase::test_report_quiet tests/test_black.py::BlackTestCase::test_report_verbose tests/test_black.py::BlackTestCase::test_required_version_does_not_match_version tests/test_black.py::BlackTestCase::test_required_version_matches_version tests/test_black.py::BlackTestCase::test_root_logger_not_used_directly tests/test_black.py::BlackTestCase::test_single_file_force_py36 tests/test_black.py::BlackTestCase::test_single_file_force_pyi tests/test_black.py::BlackTestCase::test_skip_magic_trailing_comma tests/test_black.py::BlackTestCase::test_string_quotes tests/test_black.py::BlackTestCase::test_tab_comment_indentation tests/test_black.py::BlackTestCase::test_trailing_comma_optional_parens_stability1_pass2 tests/test_black.py::BlackTestCase::test_trailing_comma_optional_parens_stability2_pass2 tests/test_black.py::BlackTestCase::test_trailing_comma_optional_parens_stability3_pass2 tests/test_black.py::BlackTestCase::test_works_in_mono_process_only_environment tests/test_black.py::TestCaching::test_get_cache_dir tests/test_black.py::TestCaching::test_cache_broken_file tests/test_black.py::TestCaching::test_cache_single_file_already_cached tests/test_black.py::TestCaching::test_cache_multiple_files tests/test_black.py::TestCaching::test_no_cache_when_writeback_diff[no-color] tests/test_black.py::TestCaching::test_no_cache_when_writeback_diff[with-color] tests/test_black.py::TestCaching::test_output_locking_when_writeback_diff[no-color] tests/test_black.py::TestCaching::test_output_locking_when_writeback_diff[with-color] tests/test_black.py::TestCaching::test_no_cache_when_stdin tests/test_black.py::TestCaching::test_read_cache_no_cachefile tests/test_black.py::TestCaching::test_write_cache_read_cache tests/test_black.py::TestCaching::test_filter_cached tests/test_black.py::TestCaching::test_write_cache_creates_directory_if_needed tests/test_black.py::TestCaching::test_failed_formatting_does_not_get_cached tests/test_black.py::TestCaching::test_write_cache_write_fail tests/test_black.py::TestCaching::test_read_cache_line_lengths tests/test_black.py::TestFileCollection::test_include_exclude tests/test_black.py::TestFileCollection::test_gitignore_used_as_default tests/test_black.py::TestFileCollection::test_exclude_for_issue_1572 tests/test_black.py::TestFileCollection::test_gitignore_exclude tests/test_black.py::TestFileCollection::test_nested_gitignore tests/test_black.py::TestFileCollection::test_invalid_gitignore tests/test_black.py::TestFileCollection::test_invalid_nested_gitignore tests/test_black.py::TestFileCollection::test_empty_include tests/test_black.py::TestFileCollection::test_extend_exclude tests/test_black.py::TestFileCollection::test_symlink_out_of_root_directory tests/test_black.py::TestFileCollection::test_get_sources_with_stdin tests/test_black.py::TestFileCollection::test_get_sources_with_stdin_filename tests/test_black.py::TestFileCollection::test_get_sources_with_stdin_filename_and_exclude tests/test_black.py::TestFileCollection::test_get_sources_with_stdin_filename_and_extend_exclude tests/test_black.py::TestFileCollection::test_get_sources_with_stdin_filename_and_force_exclude