Интеграции и практические примеры
Пример 1: Обход директории с фильтрацией
Пример 2: Резолюция путей в конфигурации приложения
Этот паттерн защищает от Path Traversal (атака обхода пути), когда злоумышленник передаёт
Пример 3: Копирование с проверкой
Пример 4: Работа с временными файлами
Path vs File: ключевые различия
Класс java.io.File (существует с Java 1.0) имеет ряд архитектурных недостатков:
Смешение ответственности:
Ограниченная поддержка символических ссылок:
Отсутствие нормализации:
Нет поддержки файловых систем, отличных от дефолтной:
Методы
#Java #для_новичков #beginner #IO #NIO #Path
Пример 1: Обход директории с фильтрацией
import java.nio.file.*;
import java.io.IOException;
public class DirectoryScanner {
public static void scanDirectory(Path root) throws IOException {
// Files.walk — рекурсивный обход дерева директорий
try (Stream<Path> paths = Files.walk(root, 3)) { // глубина 3 уровня
paths.filter(Files::isRegularFile) // только файлы
.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> System.out.println(p.toAbsolutePath()));
}
}
}
Files.walk() возвращает Stream<Path> — ленивый поток (lazy stream), который читает директории по мере необходимости. Это предотвращает загрузку всех путей в память сразу, что критично для больших файловых деревьев.Пример 2: Резолюция путей в конфигурации приложения
public class ConfigResolver {
private final Path baseDir;
public ConfigResolver(String basePath) {
this.baseDir = Paths.get(basePath).toAbsolutePath().normalize();
}
public Path resolveConfig(String configName) {
// resolve объединяет baseDir с относительным путём
Path resolved = baseDir.resolve("config").resolve(configName);
// безопасность: проверяем, что результирующий путь всё ещё внутри baseDir
if (!resolved.startsWith(baseDir)) {
throw new SecurityException("Path traversal attempt detected");
}
return resolved;
}
}Этот паттерн защищает от Path Traversal (атака обхода пути), когда злоумышленник передаёт
../../../etc/passwd в качестве configName. Метод startsWith() гарантирует, что результирующий путь остаётся внутри разрешённой директории.Пример 3: Копирование с проверкой
public void safeCopy(Path source, Path target) throws IOException {
// normalize убирает . и .. перед проверкой
Path normalizedSource = source.toAbsolutePath().normalize();
Path normalizedTarget = target.toAbsolutePath().normalize();
// getParent возвращает директорию назначения
Path targetDir = normalizedTarget.getParent();
if (targetDir != null && !Files.exists(targetDir)) {
Files.createDirectories(targetDir); // создаём всю цепочку директорий
}
// StandardCopyOption — перечисление, определяющее поведение копирования
Files.copy(normalizedSource, normalizedTarget,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
}StandardCopyOption — это enum (перечисление), тип данных в Java, представляющий фиксированный набор констант. REPLACE_EXISTING разрешает перезапись существующего файла, COPY_ATTRIBUTES копирует метаданные (права доступа, временные метки).Пример 4: Работа с временными файлами
public Path createTempFileWithPath() throws IOException {
// getDefault() возвращает файловую систему по умолчанию
Path tempDir = FileSystems.getDefault().getPath(System.getProperty("java.io.tmpdir"));
// createTempFile создаёт файл с префиксом и суффиксом
Path tempFile = Files.createTempFile(tempDir, "app_", ".tmp");
System.out.println("Created: " + tempFile.toAbsolutePath());
return tempFile;
}System.getProperty("java.io.tmpdir") возвращает путь к временной директории ОС. Files.createTempFile гарантирует уникальность имени через атомарную операцию на уровне файловой системы.Path vs File: ключевые различия
Класс java.io.File (существует с Java 1.0) имеет ряд архитектурных недостатков:
Смешение ответственности:
File одновременно представляет и путь, и файл, и директорию. Path разделяет эти концепции.Ограниченная поддержка символических ссылок:
File не различает символическую ссылку и целевой файл. Path в сочетании с Files предоставляет полный контроль.Отсутствие нормализации:
File не предоставляет методов для работы с . и ... Path.normalize() решает это.Нет поддержки файловых систем, отличных от дефолтной:
Path работает с любой FileSystem — ZIP, память, сетевые ФС.Методы
File возвращают boolean вместо исключений: file.delete() возвращает false при ошибке, не объясняя причину. Files.delete(path) выбрасывает IOException с деталями.#Java #для_новичков #beginner #IO #NIO #Path
👍5
Что выведет код?
#Tasks
import java.nio.file.Path;
import java.nio.file.Paths;
public class Task070726 {
public static void main(String[] args) {
Path p1 = Paths.get("/foo");
Path p2 = Paths.get("/bar");
Path p3 = p1.resolve(p2);
Path p4 = p1.resolve("bar");
System.out.print(p3 + " " + p4);
}
}
#Tasks
👍3
Варианты ответа:
Anonymous Quiz
22%
/foo/bar /bar
11%
/bar /foo/bar
44%
/foo/bar /foo/bar
22%
/bar /bar
👍3
Что такое 🤓
Ответ:
В Java 16 добавлен метод Stream.toList() (без параметров), который возвращает неизменяемый список (immutable).
В отличие от collect(Collectors.toList()), который возвращает мутабельный список (обычно ArrayList). toList() гарантирует, что список нельзя изменить (выбросит UnsupportedOperationException).
Это экономит память и улучшает безопасность. Для мутабельного списка всё ещё используйте collect(Collectors.toCollection(ArrayList::new)) или collect(Collectors.toList()).
#собеседование
Stream.toList() и чем отличается от collect(Collectors.toList())? Ответ:
В отличие от collect(Collectors.toList()), который возвращает мутабельный список (обычно ArrayList). toList() гарантирует, что список нельзя изменить (выбросит UnsupportedOperationException).
Это экономит память и улучшает безопасность. Для мутабельного списка всё ещё используйте collect(Collectors.toCollection(ArrayList::new)) или collect(Collectors.toList()).
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологии сегодня — 8 июля
ℹ️ Кто родился в этот день
И́горь Евге́ньевич Тамм (26 июня (8 июля) 1895 года, Владивосток — 12 апреля 1971, Москва) — советский физик-теоретик, лауреат Нобелевской премии по физике (1958). Основные направления научного творчества Тамма относятся к квантовой механике, физике твёрдого тела, теории излучения, ядерной физике, физике элементарных частиц, а также к решению ряда прикладных задач. В 1932 году опубликовал работу, в которой теоретически предсказал существование поверхностных состояний на поверхности твёрдого тела (этот вид поверхностных состояний сейчас известен как состояния Тамма).
Пётр Леони́дович Капи́ца (26 июня [8 июля] 1894, Кронштадт — 8 апреля 1984, Москва) — советский физик, инженер и инноватор, нобелевский лауреат (1978). Лауреат Нобелевской премии по физике за открытие явления сверхтекучести жидкого гелия, ввёл в научный обиход термин «сверхтекучесть». Известен также работами в области физики низких температур, изучении сверхсильных магнитных полей и удержания высокотемпературной плазмы. Разработал высокопроизводительную промышленную установку для сжижения воздуха на базе турбодетандера.
(Почитайте биографию, как решение властей СССР изменило жизнь человека во благо страны)
Никола́й Никола́евич Бенардо́с (8 (26) июня 1842, Бенардосовка, Херсонская губерния — 21 (8) сентября 1905, Фастов, Киевская губерния) — русский инженер и изобретатель греческого происхождения, создатель электрической дуговой сварки (метод «Электрогефест», 1881). Автор около 200 изобретений в различных областях техники, ему также принадлежит разработка точечной и шовной контактной сварки.
Граф Фердина́нд Адо́льф Ге́нрих А́вгуст фон Це́ппелин (нем. Ferdinand Adolf Heinrich August Graf von Zeppelin; 8 июля 1838, Констанц, Великое герцогство Баден, Германский союз — 8 марта 1917, Берлин, Германская империя) — немецкий изобретатель и военный деятель, конструктор многочисленных дирижаблей.
🌐 Знаковые события
2011 — начался последний полёт шаттла «Атлантис», завершивший эксплуатацию кораблей системы «Space Shuttle».
#Biography #Birth_Date #Events #08июля
И́горь Евге́ньевич Тамм (26 июня (8 июля) 1895 года, Владивосток — 12 апреля 1971, Москва) — советский физик-теоретик, лауреат Нобелевской премии по физике (1958). Основные направления научного творчества Тамма относятся к квантовой механике, физике твёрдого тела, теории излучения, ядерной физике, физике элементарных частиц, а также к решению ряда прикладных задач. В 1932 году опубликовал работу, в которой теоретически предсказал существование поверхностных состояний на поверхности твёрдого тела (этот вид поверхностных состояний сейчас известен как состояния Тамма).
Пётр Леони́дович Капи́ца (26 июня [8 июля] 1894, Кронштадт — 8 апреля 1984, Москва) — советский физик, инженер и инноватор, нобелевский лауреат (1978). Лауреат Нобелевской премии по физике за открытие явления сверхтекучести жидкого гелия, ввёл в научный обиход термин «сверхтекучесть». Известен также работами в области физики низких температур, изучении сверхсильных магнитных полей и удержания высокотемпературной плазмы. Разработал высокопроизводительную промышленную установку для сжижения воздуха на базе турбодетандера.
(Почитайте биографию, как решение властей СССР изменило жизнь человека во благо страны)
Никола́й Никола́евич Бенардо́с (8 (26) июня 1842, Бенардосовка, Херсонская губерния — 21 (8) сентября 1905, Фастов, Киевская губерния) — русский инженер и изобретатель греческого происхождения, создатель электрической дуговой сварки (метод «Электрогефест», 1881). Автор около 200 изобретений в различных областях техники, ему также принадлежит разработка точечной и шовной контактной сварки.
Граф Фердина́нд Адо́льф Ге́нрих А́вгуст фон Це́ппелин (нем. Ferdinand Adolf Heinrich August Graf von Zeppelin; 8 июля 1838, Констанц, Великое герцогство Баден, Германский союз — 8 марта 1917, Берлин, Германская империя) — немецкий изобретатель и военный деятель, конструктор многочисленных дирижаблей.
2011 — начался последний полёт шаттла «Атлантис», завершивший эксплуатацию кораблей системы «Space Shuttle».
#Biography #Birth_Date #Events #08июля
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
[Совет по Java #058]
Тема:
Проблема:
Если разработчик забывает это сделать или между
В отличие от
Решение: Всегда обрамляйте вызов
Это гарантирует, что блокировка будет освобождена даже при возникновении исключения. Для операций с таймаутами используйте
При работе с
Объяснение:
Блок
#Java #советы
Тема:
ReentrantLock не освобождается автоматически (в отличие от synchronized).Проблема:
ReentrantLock требует явного вызова метода unlock() для освобождения блокировки. Если разработчик забывает это сделать или между
lock() и unlock() возникает исключение, блокировка остается захваченной навсегда. Это приводит к взаимоблокировкам (deadlock) и остановке потоков, которые пытаются захватить эту блокировку. В отличие от
synchronized, который автоматически освобождает монитор при выходе из блока (включая исключения), ReentrantLock не предоставляет такой гарантии. Ошибки ручного управления особенно опасны в сложных цепочках вызовов, где легко пропустить unlock. Это делает ReentrantLock более гибким, но и более рискованным инструментом.Решение: Всегда обрамляйте вызов
lock() конструкцией try-finally, где unlock() вызывается в блоке finally. Это гарантирует, что блокировка будет освобождена даже при возникновении исключения. Для операций с таймаутами используйте
tryLock(long, TimeUnit), который возвращает boolean, и проверяйте результат. В высоконагруженных системах применяйте tryLock() с таймаутом и повторными попытками для предотвращения бесконечного ожидания. При работе с
Condition также требуется try-finally. Для упрощения кода можно использовать вспомогательные методы, но они не отменяют необходимости явного освобождения.import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class ReentrantLockFinally {
private final ReentrantLock lock = new ReentrantLock();
//Антипаттерн: без finally — риск deadlock
public void badMethod() {
lock.lock();
// Если здесь произойдет исключение, lock не освободится
doWork();
lock.unlock(); // Может не выполниться
}
//Правильно: try-finally
public void goodMethod() {
lock.lock();
try {
doWork();
} finally {
lock.unlock(); // Гарантированно вызовется
}
}
//tryLock с таймаутом
public boolean tryLockWithTimeout() throws InterruptedException {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
doWork();
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("Не удалось захватить блокировку");
return false;
}
}
//Использование Condition (тоже с finally)
public void conditionExample() throws InterruptedException {
var condition = lock.newCondition();
lock.lock();
try {
condition.await(1, TimeUnit.SECONDS);
} finally {
lock.unlock();
}
}
private void doWork() {
// критическая секция
}
}
Объяснение:
ReentrantLock — это явная блокировка, предоставляющая расширенные возможности: прерывание ожидания, таймауты, опрос, несколько условий. Однако эти возможности требуют ответственного управления. Блок
finally гарантирует, что unlock() будет вызван независимо от того, завершится ли защищаемый код успешно или с ошибкой. Если блокировка не освобождена, другие потоки, вызывающие lock(), будут заблокированы навсегда. В отличие от synchronized, где компилятор и JVM обеспечивают освобождение, в ReentrantLock это полностью на совести разработчика. #Java #советы
👍5
Что выведет код?
#Tasks
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class Task080726 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
System.out.println("Thread1 acquired lock");
});
Thread t2 = new Thread(() -> {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread2 acquired lock");
lock.unlock();
} else {
System.out.println("Thread2 failed to acquire lock");
}
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
});
t1.start();
Thread.sleep(100);
t2.start();
t2.join();
}
}
#Tasks
👍2
Что такое 🤓
Ответ:
Collectors.groupingBy(Function classifier) — собирает элементы в Map, где ключ — результат классификатора, значение — список элементов. Есть перегрузки с downstream коллектором (например, counting(), summingInt()).
Collectors.partitioningBy(Predicate) — разбивает на две части (true/false), возвращая Map<Boolean, List>.
Оба мощные инструменты для статистики и отчетов. Пример: группировка сотрудников по департаментам, подсчет количества, суммы зарплат и т.д. Основа для data-driven операций.
#собеседование
Collectors.groupingBy() и partitioningBy()? Ответ:
Collectors.groupingBy(Function classifier)
Collectors.partitioningBy(Predicate) — разбивает на две части (true/false), возвращая Map<Boolean, List>.
Оба мощные инструменты для статистики и отчетов. Пример: группировка сотрудников по департаментам, подсчет количества, суммы зарплат и т.д. Основа для data-driven операций.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологии сегодня — 9 июля
ℹ️ Кто родился в этот день
Бенджамин (Бен) Рой Мо́ттельсон (англ. Benjamin Roy Mottelson; 9 июля 1926, Чикаго, Иллинойс — 13 мая 2022, Копенгаген) — американо-датский физик-теоретик и физик-ядерщик. Лауреат Нобелевской премии по физике «за открытие взаимосвязи между коллективным движением и движением отдельной частицы в атомном ядре и развитие теории строения атомного ядра, базирующейся на этой взаимосвязи».
Томас Дэвенпорт (англ. Thomas Davenport; 9 июля 1802 — 6 июля 1851) — американский кузнец, изобретатель. В 1833 году сконструировал первый роторный электродвигатель постоянного тока, создал приводимую им в движение модель поезда. В 1837 году получил патент на электромагнитную машину.
🌐 Знаковые события
1957 — открыт 102-й элемент таблицы Менделеева — нобелий.
#Biography #Birth_Date #Events #09июля
Бенджамин (Бен) Рой Мо́ттельсон (англ. Benjamin Roy Mottelson; 9 июля 1926, Чикаго, Иллинойс — 13 мая 2022, Копенгаген) — американо-датский физик-теоретик и физик-ядерщик. Лауреат Нобелевской премии по физике «за открытие взаимосвязи между коллективным движением и движением отдельной частицы в атомном ядре и развитие теории строения атомного ядра, базирующейся на этой взаимосвязи».
Томас Дэвенпорт (англ. Thomas Davenport; 9 июля 1802 — 6 июля 1851) — американский кузнец, изобретатель. В 1833 году сконструировал первый роторный электродвигатель постоянного тока, создал приводимую им в движение модель поезда. В 1837 году получил патент на электромагнитную машину.
1957 — открыт 102-й элемент таблицы Менделеева — нобелий.
#Biography #Birth_Date #Events #09июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)
Глава 2. Современный NIO.2 (java.nio.file)
Класс Files — утилитный класс для работы с файлами
Класс
Утилитный класс (utility class) — это класс, содержащий только статические методы и константы, не предназначенный для создания экземпляров. Обычно он имеет приватный конструктор, чтобы предотвратить инстанцирование.
Архитектура: почему Files лучше java.io.File
Старый класс
Разделение ответственности
Исключения вместо молчаливых ошибок
Методы
Методы
Checked-исключение — это исключение в Java, которое компилятор требует либо обработать в блоке
Поддержка символических ссылок
Символическая ссылка (symbolic link, symlink) — это специальный файл в файловой системе, содержащий путь к другому файлу или директории. В отличие от жёсткой ссылки, символическая ссылка может указывать на несуществующий объект и может пересекать границы файловых систем.
Атомарность и конкурентность
Многие методы
Атомарная операция — это операция, которая либо выполняется целиком, либо не выполняется вообще; промежуточных состояний не существует. В контексте файловых систем это означает, что другие процессы не увидят частичного результата.
Проверка существования и типа файла
Files.exists(Path path, LinkOption... options)
Метод проверяет, существует ли файл, директория или символическая ссылка по указанному пути. Возвращает
Важный нюанс:
Параметр
Files.notExists(Path path, LinkOption... options)
Этот метод возвращает
Файл не существует — возвращает
Файл существует, но нет прав на проверку — возвращает
Произошла другая ошибка ввода-вывода — выбрасывает
Эта пара методов реализует трёхзначную логику существования: DEFINITELY_EXISTS, DEFINITELY_NOT_EXISTS, UNKNOWN. Такой подход необходим в системах, где безопасность критична: например, при проверке перед созданием файла, чтобы избежать перезаписи существующего файла, к которому у вас нет прав на чтение, но есть права на запись.
#Java #для_новичков #beginner #IO #NIO #Files
Глава 2. Современный NIO.2 (java.nio.file)
Класс Files — утилитный класс для работы с файлами
Класс
Files из пакета java.nio.file — это центральная утилита в NIO.2, появившаяся в Java 7. Его роль в файловом API аналогична роли Collections для коллекций или Arrays для массивов: он предоставляет исключительно статические методы, которые выполняют операции над объектами Path. Если Path — это адрес, то Files — это набор инструментов, которые позволяют по этому адресу что-либо сделать.Утилитный класс (utility class) — это класс, содержащий только статические методы и константы, не предназначенный для создания экземпляров. Обычно он имеет приватный конструктор, чтобы предотвратить инстанцирование.
Files именно такой: вы не пишете new Files(), а вызываете Files.exists(path).Архитектура: почему Files лучше java.io.File
Старый класс
java.io.File существует с первой версии Java (1996 год) и несёт в себе архитектурные компромиссы того времени. Класс Files вместе с Path решает эти проблемы системно.Разделение ответственности
java.io.File смешивал в себе три роли: представление пути, проверку метаданных и выполнение операций. Path отвечает только за путь, а Files — только за операции. Это соответствует принципу SRP (Single Responsibility Principle, принцип единственной ответственности): каждый класс должен иметь только одну причину для изменения.Исключения вместо молчаливых ошибок
Методы
File возвращали boolean при неудаче, не объясняя причину:java.io.File oldFile = new java.io.File("/etc/shadow");
boolean deleted = oldFile.delete(); // false — но почему? Нет прав? Файл занят? Файла нет?Методы
Files выбрасывают checked-исключения (проверяемые исключения) — подклассы IOException, которые содержат точную причину отказа: NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException и другие. Это позволяет писать надёжный код с ветвлением по конкретным ошибкам.Checked-исключение — это исключение в Java, которое компилятор требует либо обработать в блоке
try-catch, либо объявить в сигнатуре метода через throws. Модель checked/unchecked исключений в Java разделяет ошибки, от которых вызывающий код теоретически может восстановиться (checked), и программные ошибки (unchecked, наследники RuntimeException).Поддержка символических ссылок
File не различал символическую ссылку (symlink) и целевой файл. Files предоставляет методы, явно работающие с ссылками: isSymbolicLink(), readSymbolicLink(), а также варианты методов с параметром LinkOption.NOFOLLOW_LINKS, который указывает, что операция должна применяться к самой ссылке, а не к тому, на что она указывает.Символическая ссылка (symbolic link, symlink) — это специальный файл в файловой системе, содержащий путь к другому файлу или директории. В отличие от жёсткой ссылки, символическая ссылка может указывать на несуществующий объект и может пересекать границы файловых систем.
Атомарность и конкурентность
Многие методы
Files гарантируют атомарность на уровне файловой системы. Например, Files.move() с опцией ATOMIC_MOVE выполняет перемещение как неделимую операцию, что невозможно было реализовать через старый File.renameTo().Атомарная операция — это операция, которая либо выполняется целиком, либо не выполняется вообще; промежуточных состояний не существует. В контексте файловых систем это означает, что другие процессы не увидят частичного результата.
Проверка существования и типа файла
Files.exists(Path path, LinkOption... options)
Метод проверяет, существует ли файл, директория или символическая ссылка по указанному пути. Возвращает
true, если файл существует, false — если нет или если при проверке произошла ошибка (например, отказ в доступе).Path configPath = Paths.get("/app/config.properties");
boolean exists = Files.exists(configPath);Важный нюанс:
Files.exists() возвращает false не только когда файла нет, но и когда нет прав на проверку. Это делает метод неидеальным для однозначных выводов. Если вам нужно различать "файла нет" и "нет прав", используйте Files.notExists() в паре с Files.exists().Параметр
LinkOption... options — это varargs массив опций. Передача LinkOption.NOFOLLOW_LINKS означает, что если путь — символическая ссылка, метод проверит существование самой ссылки, а не целевого файла.Files.notExists(Path path, LinkOption... options)
Этот метод возвращает
true, если файл точно не существует. Он отличается от простой инверсии !Files.exists() тем, что различает два состояния:Файл не существует — возвращает
true.Файл существует, но нет прав на проверку — возвращает
false.Произошла другая ошибка ввода-вывода — выбрасывает
IOException.Path secret = Paths.get("/root/.ssh/id_rsa");
if (Files.notExists(secret)) {
// Мы уверены, что файла нет (или нет прав, но тогда exists тоже вернул бы false)
System.out.println("Файл не существует");
} else if (Files.exists(secret)) {
// Мы уверены, что файл есть
System.out.println("Файл существует");
} else {
// Files.exists() == false И Files.notExists() == false
// Это возможно только при ошибке доступа — состояние неопределённости
System.out.println("Невозможно определить существование файла");
}Эта пара методов реализует трёхзначную логику существования: DEFINITELY_EXISTS, DEFINITELY_NOT_EXISTS, UNKNOWN. Такой подход необходим в системах, где безопасность критична: например, при проверке перед созданием файла, чтобы избежать перезаписи существующего файла, к которому у вас нет прав на чтение, но есть права на запись.
#Java #для_новичков #beginner #IO #NIO #Files
👍3
Files.isDirectory(Path path, LinkOption... options)
Проверяет, является ли путь директорией. Возвращает
Files.isRegularFile(Path path, LinkOption... options)
Проверяет, является ли путь обычным файлом. Обычный файл (regular file) — это файл, который не является директорией, символической ссылкой, именованным каналом, сокетом домена Unix или устройством. Это файл с данными, который можно читать и писать.
Files.isSymbolicLink(Path path)
Проверяет, является ли путь символической ссылкой. Этот метод не следует по ссылке — он проверяет сам объект файловой системы. Если путь — symlink, метод вернёт
Практический пример: инициализация конфигурации
Рассмотрим сценарий, в котором приложение при старте проверяет файл конфигурации. Если файл отсутствует — создаёт его с дефолтными настройками. Если файл существует, но является директорией или символической ссылкой — обрабатывает это как ошибку.
В этом примере последовательность проверок критична: сначала
Путь байтов в памяти JVM при работе с Files
Каждый вызов метода
1. Объект Path в куче
Объект
2. Переход в нативный код через JNI
Реализация
Этот массив байтов создаётся в куче как временный объект
3. Системный вызов и кэш страниц ОС
ОС получает путь в виде байтов и выполняет системный вызов
Данные файла читаются не напрямую с диска, а через кэш страниц (page cache) — область оперативной памяти ядра, где хранятся недавно использованные блоки файлов. Если данные есть в кэше, чтение происходит без обращения к диску. Если нет — происходит промах кэша, и данные загружаются с диска в кэш страниц.
4. Возврат данных в кучу JVM
Прочитанные байты копируются из кэша страниц ОС через нативную память обратно в кучу JVM. Метод
G1 GC (Garbage-First Garbage Collector) — это сборщик мусора, используемый по умолчанию с Java 9. Он делит кучу на регионы фиксированного размера (обычно 1–32 МБ). Объект, занимающий более половины региона, считается humongous и размещается непрерывным блоком в Old Generation.
5. Работа GC с файловыми данными
После возврата из
Важно: сам файловый дескриптор, открытый ОС, закрывается нативным кодом до возврата в Java. Но если вы используете потоковое чтение (
#Java #для_новичков #beginner #IO #NIO #Files
Проверяет, является ли путь директорией. Возвращает
false, если путь — файл, символическая ссылка на файл, или если проверка невозможна. Для корректной работы требуется, чтобы файл существовал: метод не выбрасывает исключение при отсутствии файла, но возвращает false.Path dir = Paths.get("/var/log");
if (Files.isDirectory(dir)) {
// Это директория
}Files.isRegularFile(Path path, LinkOption... options)
Проверяет, является ли путь обычным файлом. Обычный файл (regular file) — это файл, который не является директорией, символической ссылкой, именованным каналом, сокетом домена Unix или устройством. Это файл с данными, который можно читать и писать.
Path file = Paths.get("/etc/passwd");
if (Files.isRegularFile(file)) {
// Это обычный файл, можно безопасно читать
}Files.isSymbolicLink(Path path)
Проверяет, является ли путь символической ссылкой. Этот метод не следует по ссылке — он проверяет сам объект файловой системы. Если путь — symlink, метод вернёт
true даже если целевой файл удалён (висячая ссылка).Path link = Paths.get("/usr/bin/java");
if (Files.isSymbolicLink(link)) {
Path target = Files.readSymbolicLink(link); // куда указывает ссылка
System.out.println("Ссылка ведёт на: " + target);
}Практический пример: инициализация конфигурации
Рассмотрим сценарий, в котором приложение при старте проверяет файл конфигурации. Если файл отсутствует — создаёт его с дефолтными настройками. Если файл существует, но является директорией или символической ссылкой — обрабатывает это как ошибку.
import java.nio.file.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ConfigInitializer {
private static final Path CONFIG_PATH = Paths.get("app.conf");
private static final String DEFAULT_CONFIG =
"# Default configuration\n" +
"server.port=8080\n" +
"db.url=jdbc:h2:mem:test\n";
public void initialize() throws IOException {
// Шаг 1: проверяем, существует ли путь
if (Files.notExists(CONFIG_PATH)) {
// Файла точно нет — создаём с дефолтами
createDefaultConfig();
System.out.println("Конфигурация создана по умолчанию");
return;
}
// Шаг 2: файл существует — проверяем, что это обычный файл
if (!Files.isRegularFile(CONFIG_PATH)) {
if (Files.isDirectory(CONFIG_PATH)) {
throw new IOException(
"Конфигурационный путь \"" + CONFIG_PATH + "\" является директорией"
);
}
if (Files.isSymbolicLink(CONFIG_PATH)) {
// Разрешаем символическую ссылку, но проверяем целевой объект
Path target = Files.readSymbolicLink(CONFIG_PATH);
if (!Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException(
"Символическая ссылка конфигурации указывает на не-файл: " + target
);
}
// Ссылка валидна — продолжаем
return;
}
throw new IOException("Неизвестный тип файла конфигурации");
}
// Шаг 3: обычный файл существует — проверяем читаемость
if (!Files.isReadable(CONFIG_PATH)) {
throw new AccessDeniedException("Нет прав на чтение конфигурации");
}
System.out.println("Конфигурация загружена из существующего файла");
}
private void createDefaultConfig() throws IOException {
// getParent() может вернуть null, если путь в текущей директории
Path parent = CONFIG_PATH.getParent();
if (parent != null && Files.notExists(parent)) {
// createDirectories создаёт всю иерархию, включая промежуточные директории
Files.createDirectories(parent);
}
// write записывает массив байтов в файл, создавая его атомарно
Files.write(CONFIG_PATH, DEFAULT_CONFIG.getBytes(StandardCharsets.UTF_8));
}
}
В этом примере последовательность проверок критична: сначала
notExists гарантирует, что мы не перезапишем существующий файл. Затем isRegularFile защищает от директорий и ссылок. Files.isReadable() — дополнительная проверка, которая не выбрасывает исключение, а возвращает boolean, что удобно для ветвления логики.Путь байтов в памяти JVM при работе с Files
Каждый вызов метода
Files порождает цепочку преобразований данных от Java-объекта до системного вызова ОС и обратно. Разберём этот путь на примере Files.readAllBytes(path).1. Объект Path в куче
Объект
Path, переданный в метод, уже находится в куче. Как описано в предыдущем уроке, Path — это лёгкая структура, хранящая сегменты пути и ссылку на FileSystem. При вызове Files.readAllBytes(path) JVM помещает ссылку на Path в стек вызовов текущего потока — это область памяти, где хранятся локальные переменные и параметры методов. Стек потока выделяется в нативной памяти и не управляется GC напрямую.2. Переход в нативный код через JNI
Реализация
Files.readAllBytes() в OpenJDK делегирует работу классу FileChannel или прямому нативному вызову через JNI (Java Native Interface — механизм взаимодействия Java-кода с библиотеками, написанными на C/C++ или ассемблере). На этом этапе строковое представление пути извлекается из объекта Path и кодируется в массив байтов в кодировке UTF-8.Этот массив байтов создаётся в куче как временный объект
byte[] или char[]. Затем он копируется в нативную память (native memory, память вне кучи JVM) через JNI-функции, так как системные вызовы ОС работают с C-строками (char* или wchar_t*), а не с Java-объектами.3. Системный вызов и кэш страниц ОС
ОС получает путь в виде байтов и выполняет системный вызов
open() (POSIX) или NtCreateFile() (Windows). Ядро ОС преобразует путь в структуру inode (на Unix) или MFT-запись (на NTFS), проверяет права доступа и открывает файловый дескриптор.Данные файла читаются не напрямую с диска, а через кэш страниц (page cache) — область оперативной памяти ядра, где хранятся недавно использованные блоки файлов. Если данные есть в кэше, чтение происходит без обращения к диску. Если нет — происходит промах кэша, и данные загружаются с диска в кэш страниц.
4. Возврат данных в кучу JVM
Прочитанные байты копируются из кэша страниц ОС через нативную память обратно в кучу JVM. Метод
readAllBytes() выделяет byte[] точного размера файла. Этот массив создаётся в Young Generation (молодом поколении) кучи, в области Eden. Если файл большой (например, 100 МБ), массив может оказаться слишком крупным для Young Generation и будет сразу размещён в Old Generation (старом поколении) — такие объекты называются humongous objects (гигантские объекты) в G1 GC.G1 GC (Garbage-First Garbage Collector) — это сборщик мусора, используемый по умолчанию с Java 9. Он делит кучу на регионы фиксированного размера (обычно 1–32 МБ). Объект, занимающий более половины региона, считается humongous и размещается непрерывным блоком в Old Generation.
5. Работа GC с файловыми данными
После возврата из
readAllBytes() массив байтов остаётся в куче. Если он присвоен локальной переменной внутри метода и метод завершился, ссылка на массив исчезает из стека, и массив становится мусором. Minor GC (молодая сборка) соберёт его при следующем проходе, если он находится в Eden. Если массив большой и попал в Old Generation, он дождётся Major GC (полной сборки).Важно: сам файловый дескриптор, открытый ОС, закрывается нативным кодом до возврата в Java. Но если вы используете потоковое чтение (
Files.newInputStream()), дескриптор остаётся открытым до вызова close(). Если забыть закрыть поток, дескриптор утечёт на уровне ОС, даже если Java-объект InputStream будет собран GC. GC не знает о ресурсах ОС — он управляет только памятью кучи. Поэтому try-with-resources обязателен:// try-with-resources гарантирует вызов close() даже при исключении
// AutoCloseable — интерфейс, требующий реализации метода close()
try (InputStream in = Files.newInputStream(path)) {
// работа с потоком
} // close() вызывается автоматически здесь
#Java #для_новичков #beginner #IO #NIO #Files
👍5
6. Метаданные и временные объекты
При вызовах
Чтение и запись данных
Files.readAllBytes(Path) и Files.readAllLines(Path, Charset)
Ленивый поток — это поток данных, элементы которого вычисляются или читаются по требованию, а не все сразу. В Java Stream API операции промежуточные (filter, map) ленивы, а терминальные (count, collect) — энергичные (запускают вычисление).
Files.write(Path, byte[]/Iterable, OpenOption...)
Копирование, перемещение и удаление
Files.copy(Path source, Path target, CopyOption... options)
Метод поддерживает копирование между разными файловыми системами (например, из ZIP-архива на диск), так как работает через абстракцию
FileSystemProvider — это сервис-провайдер (SPI, Service Provider Interface), который реализует конкретную файловую систему. Для каждой ФС существует свой провайдер:
Files.move(Path source, Path target, CopyOption... options)
Files.delete(Path path) и Files.deleteIfExists(Path path)
Работа с метаданными
Files.getAttribute и Files.setAttribute
POSIX (Portable Operating System Interface) — это семейство стандартов, определяющих интерфейс между ОС и приложениями. В контексте файлов POSIX определяет модель прав доступа (владелец, группа, остальные) и набор операций над файлами.
Files.getFileStore(Path)
FileStore — это абстракция над хранилищем файловой системы (раздел диска, том, сетевой диск). Она позволяет узнать общий и доступный объём, тип ФС и другие характеристики.
Продвинутые паттерны использования
Паттерн "Write to temp, then move"
Для атомарной записи файла пишите во временный файл в той же директории, а затем атомарно перемещайте:
Этот паттерн гарантирует, что читатели никогда не увидят частично записанный файл. Временный файл создаётся через
Паттерн "Directory watch"
WatchService — сервис наблюдения за изменениями в файловой системе. Вместо периодического опроса (polling) он использует событийную модель ОС, что экономит CPU.
#Java #для_новичков #beginner #IO #NIO #Files
При вызовах
Files.exists(), isDirectory() и подобных методов данные файла не читаются. Вместо этого выполняется системный вызов stat() (POSIX) или GetFileAttributes() (Windows), который возвращает метаданные: тип файла, права доступа, размер, временные метки. Эти метаданные возвращаются в виде структуры в нативной памяти, из которой JVM извлекает нужные поля и создаёт примитивы (boolean, long) на стеке. Никаких объектов в куче при этом не создаётся, поэтому такие проверки — "бесплатные" с точки зрения GC.Чтение и запись данных
Files.readAllBytes(Path) и Files.readAllLines(Path, Charset)
// Чтение всего файла как массива байтов
byte[] rawData = Files.readAllBytes(Paths.get("data.bin"));
// Чтение текстового файла в список строк
List<String> lines = Files.readAllLines(
Paths.get("log.txt"),
StandardCharsets.UTF_8
);
readAllBytes() удобен для небольших файлов, но опасен для больших: он загружает весь файл в память. Для больших файлов используйте Files.newBufferedReader() или Files.lines().Files.lines() возвращает Stream<String> — ленивый поток (lazy stream), который читает файл построчно по мере обхода. Это экономит память, но требует закрытия потока:// Stream<String> должен быть закрыт, так как держит файловый дескриптор
try (Stream<String> stream = Files.lines(Paths.get("huge.log"))) {
long errorCount = stream.filter(line -> line.contains("ERROR")).count();
}
Ленивый поток — это поток данных, элементы которого вычисляются или читаются по требованию, а не все сразу. В Java Stream API операции промежуточные (filter, map) ленивы, а терминальные (count, collect) — энергичные (запускают вычисление).
Files.write(Path, byte[]/Iterable, OpenOption...)
Path target = Paths.get("output.txt");
byte[] data = "Hello, World".getBytes(StandardCharsets.UTF_8);
// CREATE — создать файл, если не существует
// TRUNCATE_EXISTING — очистить существующий файл перед записью
Files.write(target, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);OpenOption — это интерфейс-маркер, который реализуют перечисления StandardOpenOption и LinkOption. Он определяет, как именно открывать или создавать файл.Копирование, перемещение и удаление
Files.copy(Path source, Path target, CopyOption... options)
Path src = Paths.get("/tmp/source.dat");
Path dst = Paths.get("/backup/source.dat");
Files.copy(src, dst,
StandardCopyOption.REPLACE_EXISTING, // перезаписать, если есть
StandardCopyOption.COPY_ATTRIBUTES // скопировать права и временные метки
);Метод поддерживает копирование между разными файловыми системами (например, из ZIP-архива на диск), так как работает через абстракцию
FileSystemProvider.FileSystemProvider — это сервис-провайдер (SPI, Service Provider Interface), который реализует конкретную файловую систему. Для каждой ФС существует свой провайдер:
UnixFileSystemProvider, WindowsFileSystemProvider, ZipFileSystemProvider и т.д. Files.copy() делегирует операцию провайдеру источника и провайдеру назначения.Files.move(Path source, Path target, CopyOption... options)
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
ATOMIC_MOVE гарантирует, что операция будет атомарной в пределах одной файловой системы. Если источник и назначение на разных ФС, метод выбросит AtomicMoveNotSupportedException.Files.delete(Path path) и Files.deleteIfExists(Path path)
// delete бросает NoSuchFileException, если файла нет
Files.delete(Paths.get("temp.tmp"));
// deleteIfExists возвращает boolean, не бросая исключение при отсутствии
boolean deleted = Files.deleteIfExists(Paths.get("temp.tmp"));
Files.delete() требует, чтобы директория была пустой. Для рекурсивного удаления используйте обход дерева:// walk возвращает Stream<Path> всех файлов и директорий, начиная с root
// sorted(Comparator.reverseOrder()) — сначала файлы, потом родительские директории
Files.walk(root)
.sorted(Comparator.reverseOrder())
.forEach(p -> {
try {
Files.delete(p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
Работа с метаданными
Files.getAttribute и Files.setAttribute
// Чтение произвольного атрибута файловой системы
Object creationTime = Files.getAttribute(path, "creationTime");
// Установка POSIX-прав доступа (только на Unix)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
Files.setPosixFilePermissions(path, perms);
POSIX (Portable Operating System Interface) — это семейство стандартов, определяющих интерфейс между ОС и приложениями. В контексте файлов POSIX определяет модель прав доступа (владелец, группа, остальные) и набор операций над файлами.
Files.getFileStore(Path)
FileStore store = Files.getFileStore(path);
long usableSpace = store.getUsableSpace();
long totalSpace = store.getTotalSpace();
FileStore — это абстракция над хранилищем файловой системы (раздел диска, том, сетевой диск). Она позволяет узнать общий и доступный объём, тип ФС и другие характеристики.
Продвинутые паттерны использования
Паттерн "Write to temp, then move"
Для атомарной записи файла пишите во временный файл в той же директории, а затем атомарно перемещайте:
public void atomicWrite(Path target, byte[] data) throws IOException {
// Создаём временный файл в той же директории, что и target
// это важно, чтобы move был атомарным в пределах одного FileStore
Path temp = Files.createTempFile(target.getParent(), "tmp_", ".dat");
try {
Files.write(temp, data);
// Атомарное переименование: либо старый файл заменён, либо нет
Files.move(temp, target, StandardCopyOption.ATOMIC_REPLACE);
} catch (IOException e) {
// При ошибке удаляем временный файл
Files.deleteIfExists(temp);
throw e;
}
}Этот паттерн гарантирует, что читатели никогда не увидят частично записанный файл. Временный файл создаётся через
Files.createTempFile(), который генерирует уникальное имя атомарно.Паттерн "Directory watch"
WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/app/inbox");
// Регистрируем директорию на отслеживание создания и модификации файлов
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY
);
// WatchService — механизм мониторинга изменений в файловой системе
// Он использует нативные API ОС (inotify на Linux, FSEvents на macOS, ReadDirectoryChangesW на Windows)
WatchService — сервис наблюдения за изменениями в файловой системе. Вместо периодического опроса (polling) он использует событийную модель ОС, что экономит CPU.
#Java #для_новичков #beginner #IO #NIO #Files
👍5
Что выведет код?
#Tasks
import java.nio.file.*;
import java.io.IOException;
public class Task090726 {
public static void main(String[] args) throws IOException {
Path p1 = Paths.get("");
Path p2 = Paths.get(".");
System.out.println(Files.isSameFile(p1, p2));
System.out.println(p1.equals(p2));
System.out.println(p1.normalize().equals(p2.normalize()));
}
}
#Tasks
👍2
Варианты ответа:
Anonymous Quiz
55%
true true true
27%
true false true
0%
false false true
18%
true false false
👍2
Ребят, продолжаю набор желающих обучиться Java. 🤓
Гарантий трудоустройства нет, но на уровень мидла точно выведу) Недорого)))
Пишите в лс! @Oleborn📞
Пишите в лс! @Oleborn
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Что такое 🤓
Ответ:
Java 10 ввел коллекторы для создания неизменяемых коллекций: Collectors.toUnmodifiableList(), toUnmodifiableSet(), toUnmodifiableMap().
Они возвращают коллекции, которые не поддерживают изменения (в отличие от toList() из Java 16, который также неизменяем, но реализован проще).
Эти коллекторы ожидают, что элементы не будут null. Используйте их, когда хотите гарантировать неизменность результата на уровне коллектора, например, при возврате из публичного метода.
#собеседование
Collectors.toUnmodifiableList() и toUnmodifiableSet()? Ответ:
Они возвращают коллекции, которые не поддерживают изменения (в отличие от toList() из Java 16, который также неизменяем, но реализован проще).
Эти коллекторы ожидают, что элементы не будут null. Используйте их, когда хотите гарантировать неизменность результата на уровне коллектора, например, при возврате из публичного метода.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологии сегодня — 10 июля
ℹ️ Кто родился в этот день
Ни́кола Те́сла (в некоторых источниках Нико́ла Те́сла, серб. Ни́кола Те́сла, англ. Nikola Tesla; 10 июля 1856, Смилян, Военная граница, Австрийская империя — 7 января 1943, Манхэттен, Нью-Йорк, США) — американский инженер и физик-электроник сербского происхождения, изобретатель в области электротехники и радиотехники, внёсший существенный вклад в развитие электроинженерии. Наиболее известен разработкой современной системы электроснабжения переменного тока. После победы в «Войне токов» и демонстрации радио стал одной из самых известных личностей в истории науки XX века.
Никола́й Алекса́ндрович Ши́лов (28 июня (10 июля) 1872, Москва — 17 августа 1930, Гагра) — русский и советский физико-химик, специалист в области теории адсорбции и реакций окисления.
Харви Болл (англ. Harvey Ross Ball; 10 июля 1921, Вустер, Массачусетс, США — 12 апреля 2001, там же) — американский графический дизайнер, создатель «смайлика», учредитель Всемирного дня улыбки (отмечается в первую пятницу октября).
О́уэн Че́мберлен (англ. Owen Chamberlain; 10 июля 1920, Сан-Франциско, США — 28 февраля 2006, Беркли, США) — американский физик, член Национальной академии наук США (1960), лауреат Нобелевской премии по физике 1959 года «за открытие антипротона» совместно с Эмилио Сегре.
🌐 Знаковые события
1962 — запущен первый коммерческий спутник связи — «Телстар».
#Biography #Birth_Date #Events #10июля
Ни́кола Те́сла (в некоторых источниках Нико́ла Те́сла, серб. Ни́кола Те́сла, англ. Nikola Tesla; 10 июля 1856, Смилян, Военная граница, Австрийская империя — 7 января 1943, Манхэттен, Нью-Йорк, США) — американский инженер и физик-электроник сербского происхождения, изобретатель в области электротехники и радиотехники, внёсший существенный вклад в развитие электроинженерии. Наиболее известен разработкой современной системы электроснабжения переменного тока. После победы в «Войне токов» и демонстрации радио стал одной из самых известных личностей в истории науки XX века.
Никола́й Алекса́ндрович Ши́лов (28 июня (10 июля) 1872, Москва — 17 августа 1930, Гагра) — русский и советский физико-химик, специалист в области теории адсорбции и реакций окисления.
Харви Болл (англ. Harvey Ross Ball; 10 июля 1921, Вустер, Массачусетс, США — 12 апреля 2001, там же) — американский графический дизайнер, создатель «смайлика», учредитель Всемирного дня улыбки (отмечается в первую пятницу октября).
О́уэн Че́мберлен (англ. Owen Chamberlain; 10 июля 1920, Сан-Франциско, США — 28 февраля 2006, Беркли, США) — американский физик, член Национальной академии наук США (1960), лауреат Нобелевской премии по физике 1959 года «за открытие антипротона» совместно с Эмилио Сегре.
1962 — запущен первый коммерческий спутник связи — «Телстар».
#Biography #Birth_Date #Events #10июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
[Совет по Java #059]
Тема:
Проблема: Метод
Это явление называется ложным пробуждением (spurious wakeup) и разрешено спецификацией для оптимизации реализации на некоторых платформах. Если после возврата из
Это приводит к нарушению инвариантов, повреждению данных и трудноуловимым ошибкам, которые проявляются крайне редко (из-за редкости ложных пробуждений) и поэтому особенно опасны.
Решение: Всегда помещайте вызов
Для обеспечения безопасности используйте
Объяснение: Спецификация Java позволяет ложные пробуждения, чтобы упростить реализацию на некоторых ОС (например, в системах с условными переменными pthreads). Даже если вероятность ложного пробуждения крайне мала, полагаться на то, что оно не произойдет, ненадежно.
Использование цикла
#Java #советы
Тема:
Condition.await() может проснуться без сигнала (spurious wakeup).Проблема: Метод
await() класса Condition (из java.util.concurrent.locks) может вернуть управление даже без вызова signal() или signalAll() на этой же Condition. Это явление называется ложным пробуждением (spurious wakeup) и разрешено спецификацией для оптимизации реализации на некоторых платформах. Если после возврата из
await() не проверить условие, ради которого поток ожидал, а сразу продолжить выполнение, то программа может выполнить критическую секцию, когда условие еще не выполнено. Это приводит к нарушению инвариантов, повреждению данных и трудноуловимым ошибкам, которые проявляются крайне редко (из-за редкости ложных пробуждений) и поэтому особенно опасны.
Решение: Всегда помещайте вызов
await() в цикл while (condition), который проверяет условие до и после пробуждения. Это гарантирует, что поток продолжит работу только когда условие действительно истинно. Эта идиома универсальна для всех реализаций Condition и рекомендуется в документации Java. Также нужно помнить, что await() может быть прерван (InterruptedException), и это следует корректно обрабатывать.Для обеспечения безопасности используйте
lock.lock() и try-finally для освобождения блокировки.import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class SpuriousWakeup {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
//Антипаттерн: if вместо цикла
public void badAwait() throws InterruptedException {
lock.lock();
try {
if (!ready) {
condition.await(); // Может проснуться ложно
}
// Здесь ready может быть false!
doWork();
} finally {
lock.unlock();
}
}
//Правильно: while цикл
public void goodAwait() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // Проверка в цикле
}
// Теперь ready гарантированно true
doWork();
} finally {
lock.unlock();
}
}
// Сигнал для изменения состояния
public void signal() {
lock.lock();
try {
ready = true;
condition.signal();
} finally {
lock.unlock();
}
}
// Демонстрация с несколькими потоками
public static void main(String[] args) throws InterruptedException {
SpuriousWakeup demo = new SpuriousWakeup();
Thread waiter = new Thread(() -> {
try {
demo.goodAwait();
System.out.println("Проснулся корректно");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
waiter.start();
Thread.sleep(100);
demo.signal(); // Разбудим
waiter.join();
}
}
Объяснение: Спецификация Java позволяет ложные пробуждения, чтобы упростить реализацию на некоторых ОС (например, в системах с условными переменными pthreads). Даже если вероятность ложного пробуждения крайне мала, полагаться на то, что оно не произойдет, ненадежно.
Использование цикла
while делает код устойчивым: поток проверяет условие до ожидания и после каждого пробуждения. Это стандартный паттерн, называемый "условная переменная" (condition variable). В цикле также проверяется наличие прерывания — если поток прерван, await() выбрасывает InterruptedException, которое должно быть обработано. Аналогично следует поступать с методами await(long time, TimeUnit unit) и awaitUntil(Date deadline).#Java #советы
👍4
Что выведет код?
#Tasks
import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;
public class Task100726 {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
lock.lock();
try {
if (!flag) {
condition.await(3, TimeUnit.SECONDS);
}
System.out.print("Done");
} catch (InterruptedException e) {
System.out.print("Interrupted");
} finally {
lock.unlock();
}
});
t.start();
t.join();
}
}
#Tasks
👍3