← Назад к вопросам
Приведи пример использования ThreadLocal
2.0 Middle🔥 61 комментариев
#Многопоточность#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Приведи пример использования ThreadLocal
Что такое ThreadLocal
ThreadLocal — это класс, который позволяет каждому потоку иметь собственное независимое значение переменной. Это полезно для изоляции данных между потоками без использования синхронизации.
Пример 1: Контекст пользователя в веб-приложении
Одна из классических задач — отслеживание текущего пользователя в многопоточном приложении (например, в сервлетах или Spring):
public class UserContext {
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
public static void clear() {
userHolder.remove(); // Важно! Предотвращает утечки памяти
}
}
public class UserService {
public void processUserRequest(User user) {
UserContext.setUser(user);
try {
// Логика обработки
String username = UserContext.getUser().getName();
System.out.println("Processing for: " + username);
} finally {
UserContext.clear(); // Очистка
}
}
}
public class AuditService {
public void logAction(String action) {
User user = UserContext.getUser();
System.out.println("User: " + user.getName() + ", Action: " + action);
}
}
Пример 2: Форматирование дат (DateFormat в Spring)
public class DateFormatterService {
// SimpleDateFormat НЕ потокобезопасен, поэтому используем ThreadLocal
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String formatDate(Date date) {
return dateFormat.get().format(date);
}
public static void cleanup() {
dateFormat.remove();
}
}
public class LoggingService {
public void logEvent(String message) {
String timestamp = DateFormatterService.formatDate(new Date());
System.out.println("[" + timestamp + "] " + message);
}
}
Пример 3: Request ID для трейсинга в микросервисах
public class RequestIdHolder {
private static final ThreadLocal<String> requestIdHolder = new ThreadLocal<>();
public static void setRequestId(String requestId) {
requestIdHolder.set(requestId);
}
public static String getRequestId() {
String id = requestIdHolder.get();
return id != null ? id : "UNKNOWN";
}
public static void clear() {
requestIdHolder.remove();
}
}
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String requestId = UUID.randomUUID().toString();
RequestIdHolder.setRequestId(requestId);
try {
chain.doFilter(request, response);
} finally {
RequestIdHolder.clear();
}
}
}
public class LoggingService {
private static final Logger logger = LoggerFactory.getLogger(LoggingService.class);
public void processData(String data) {
String requestId = RequestIdHolder.getRequestId();
logger.info("[{}] Processing: {}", requestId, data);
}
}
Пример 4: Использование ThreadLocal.withInitial (modern approach)
public class DatabaseConnectionPool {
private static final ThreadLocal<Connection> connectionHolder =
ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection("jdbc:mysql://localhost/db");
} catch (SQLException e) {
throw new RuntimeException("Failed to create connection", e);
}
});
public static Connection getConnection() {
return connectionHolder.get();
}
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
connectionHolder.remove();
}
}
⚠️ Важные моменты при использовании ThreadLocal
- Всегда вызывай remove() — иначе возникнет утечка памяти в приложениях с thread pool (Tomcat, Spring)
- В thread pool каждый поток может обрабатывать разные запросы — всегда очищай данные после использования
- Не используй в main thread — бесполезно
- Проблемы с наследованием потоков — child thread не наследует ThreadLocal родителя (для этого есть InheritableThreadLocal)
InheritableThreadLocal для наследования значений
public class ParentThreadLocal {
private static final InheritableThreadLocal<String> context =
new InheritableThreadLocal<>();
public static void setValue(String value) {
context.set(value);
}
public static String getValue() {
return context.get();
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
ParentThreadLocal.setValue("parent-value");
Thread child = new Thread(() -> {
// Child поток МОЖЕТ видеть родительское значение
System.out.println(ParentThreadLocal.getValue()); // parent-value
});
child.start();
child.join();
}
}
Заключение
ThreadLocal — мощный инструмент для управления состоянием, специфичным для потока. Основные применения: контекст пользователя, request ID, thread-safe форматирование. Ключ — не забывать очищать данные после использования!