← Назад к вопросам

Приведи пример использования 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

  1. Всегда вызывай remove() — иначе возникнет утечка памяти в приложениях с thread pool (Tomcat, Spring)
  2. В thread pool каждый поток может обрабатывать разные запросы — всегда очищай данные после использования
  3. Не используй в main thread — бесполезно
  4. Проблемы с наследованием потоков — 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 форматирование. Ключ — не забывать очищать данные после использования!

Приведи пример использования ThreadLocal | PrepBro