Java ThreadLocal
ThreadLocal Nədir?
ThreadLocal hər thread üçün ayrı-ayrı dəyər saxlayan dəyişənlər yaratmağa imkan verir. Hər thread öz local copy-sinə malik olur və digər thread-lərdən izolə olunur.
Xüsusiyyətlər:
- Hər thread öz dəyərinə malikdir
- Thread-safe (synchronization lazım deyil)
- Thread-lər arasında paylaşılmır
- Memory leak riski var (remove() çağırılmalıdır)
Basic Usage
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadId = new ThreadLocal<>();
public static void main(String[] args) {
// Thread 1
new Thread(() -> {
threadId.set(1);
System.out.println("Thread 1: " + threadId.get());
}).start();
// Thread 2
new Thread(() -> {
threadId.set(2);
System.out.println("Thread 2: " + threadId.get());
}).start();
// Output:
// Thread 1: 1
// Thread 2: 2
}
}
Initial Value ilə
class ThreadLocalWithInitial {
// withInitial() ilə default dəyər
private static ThreadLocal<String> context =
ThreadLocal.withInitial(() -> "default");
// və ya override ilə
private static ThreadLocal<Integer> counter = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void demo() {
System.out.println("Context: " + context.get()); // "default"
System.out.println("Counter: " + counter.get()); // 0
counter.set(counter.get() + 1);
System.out.println("Counter: " + counter.get()); // 1
}
}
User Context Example
class UserContext {
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setUser(User user) {
currentUser.set(user);
}
public static User getUser() {
return currentUser.get();
}
public static void clear() {
currentUser.remove(); // Memory leak qarşısını almaq üçün
}
}
class User {
private String name;
private String role;
// constructor, getters, setters
}
// İstifadə
class RequestHandler {
public void handleRequest(User user) {
try {
UserContext.setUser(user);
// Bu thread-də istənilən yerdə user-ə giriş
processRequest();
saveLog();
} finally {
UserContext.clear(); // Mütləq!
}
}
private void processRequest() {
User user = UserContext.getUser();
System.out.println("Processing for: " + user.getName());
}
private void saveLog() {
User user = UserContext.getUser();
System.out.println("Log saved for: " + user.getName());
}
}
Database Connection Per Thread
class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder =
ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb",
"user",
"password"
);
} catch (SQLException e) {
throw new RuntimeException(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(); // Mütləq
}
}
}
// İstifadə
class DatabaseWorker {
public void doWork() {
try {
Connection conn = ConnectionManager.getConnection();
// Use connection
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users");
// ...
} finally {
ConnectionManager.closeConnection();
}
}
}
SimpleDateFormat Thread-Safe Etmək
// Problem: SimpleDateFormat thread-safe deyil
class UnsafeDateFormatter {
private static final SimpleDateFormat formatter =
new SimpleDateFormat("yyyy-MM-dd");
public String format(Date date) {
return formatter.format(date); // Race condition!
}
}
// Həll: ThreadLocal istifadə edin
class SafeDateFormatter {
private static ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format(Date date) {
return formatter.get().format(date); // Thread-safe
}
public static Date parse(String dateStr) throws ParseException {
return formatter.get().parse(dateStr);
}
}
Transaction Context
class TransactionContext {
private static ThreadLocal<String> transactionId = new ThreadLocal<>();
private static ThreadLocal<Long> startTime = new ThreadLocal<>();
public static void begin() {
transactionId.set(UUID.randomUUID().toString());
startTime.set(System.currentTimeMillis());
System.out.println("Transaction started: " + transactionId.get());
}
public static String getTransactionId() {
return transactionId.get();
}
public static void commit() {
long duration = System.currentTimeMillis() - startTime.get();
System.out.println("Transaction " + transactionId.get() +
" committed in " + duration + "ms");
clear();
}
public static void rollback() {
System.out.println("Transaction " + transactionId.get() + " rolled back");
clear();
}
private static void clear() {
transactionId.remove();
startTime.remove();
}
}
// İstifadə
class Service {
public void processOrder() {
TransactionContext.begin();
try {
// İş görülür
System.out.println("Processing in transaction: " +
TransactionContext.getTransactionId());
TransactionContext.commit();
} catch (Exception e) {
TransactionContext.rollback();
}
}
}
Request ID Tracking
class RequestIdHolder {
private static ThreadLocal<String> requestId = new ThreadLocal<>();
public static void set(String id) {
requestId.set(id);
}
public static String get() {
return requestId.get();
}
public static void clear() {
requestId.remove();
}
}
class Logger {
public void log(String message) {
String requestId = RequestIdHolder.get();
System.out.println("[" + requestId + "] " + message);
}
}
// Web request handler
class RequestProcessor {
private Logger logger = new Logger();
public void handleRequest(String request) {
// Request ID set et
RequestIdHolder.set(UUID.randomUUID().toString());
try {
logger.log("Request başladı");
processRequest(request);
logger.log("Request bitdi");
} finally {
RequestIdHolder.clear(); // Mütləq
}
}
private void processRequest(String request) {
logger.log("Processing: " + request);
// İş görülür
}
}
Thread-Safe Counter
class ThreadSafeCounter {
private ThreadLocal<Integer> counter =
ThreadLocal.withInitial(() -> 0);
public void increment() {
counter.set(counter.get() + 1);
}
public int get() {
return counter.get();
}
public void reset() {
counter.remove();
}
}
// Test
public class CounterTest {
public static void main(String[] args) throws InterruptedException {
ThreadSafeCounter counter = new ThreadSafeCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
counter.increment();
}
System.out.println("Thread 1 count: " + counter.get());
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
counter.increment();
}
System.out.println("Thread 2 count: " + counter.get());
});
t1.start();
t2.start();
t1.join();
t2.join();
// Output:
// Thread 1 count: 5
// Thread 2 count: 3
}
}
InheritableThreadLocal
class InheritableExample {
// Child thread parent-in dəyərini inherit edir
private static InheritableThreadLocal<String> inheritableValue =
new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableValue.set("Parent Value");
System.out.println("Parent: " + inheritableValue.get());
new Thread(() -> {
// Child thread parent-in dəyərini alır
System.out.println("Child: " + inheritableValue.get());
// Child öz dəyərini dəyişir
inheritableValue.set("Child Value");
System.out.println("Child after set: " + inheritableValue.get());
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Parent-in dəyəri dəyişməyib
System.out.println("Parent after child: " + inheritableValue.get());
}
}
Memory Leak Prevention
class MemoryLeakExample {
private static ThreadLocal<List<String>> data =
ThreadLocal.withInitial(() -> new ArrayList<>());
// YANLIŞ - memory leak!
public void wrongUsage() {
data.get().add("item");
// remove() çağırılmır
}
// DÜZGÜN - remove() çağırılır
public void correctUsage() {
try {
data.get().add("item");
// İş görülür
} finally {
data.remove(); // Mütləq!
}
}
}
// Thread Pool ilə istifadə
class ThreadPoolExample {
private static ThreadLocal<Connection> connection = new ThreadLocal<>();
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void processTask() {
executor.submit(() -> {
try {
// Connection aç
connection.set(openConnection());
// İş görülür
doWork();
} finally {
// Mütləq cleanup
closeConnection(connection.get());
connection.remove(); // Thread pool üçün vacib!
}
});
}
private Connection openConnection() {
// Connection logic
return null;
}
private void closeConnection(Connection conn) {
// Close logic
}
private void doWork() {
// Work logic
}
}
Best Practices
-
Həmişə remove() çağırın:
try {
threadLocal.set(value);
// work
} finally {
threadLocal.remove(); // Mütləq
} -
Thread pool-da xüsusilə diqqətli olun:
// Thread reuse olunur, remove() vacibdir
executor.submit(() -> {
try {
threadLocal.set(value);
// work
} finally {
threadLocal.remove(); // Thread pool üçün critical!
}
}); -
Static olaraq declare edin:
private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); -
Initial value təyin edin:
private static ThreadLocal<List<String>> data =
ThreadLocal.withInitial(() -> new ArrayList<>());
Common Use Cases
- User context - Request handler-lərdə user məlumatları
- Transaction context - Transaction ID və məlumatları
- Database connections - Thread başına connection
- DateFormat - SimpleDateFormat thread-safe etmək
- Request tracking - Request ID, tracing
- Security context - Authentication məlumatları
Üstünlüklər və Çatışmazlıqlar
Üstünlüklər:
- Thread-safe (synchronization lazım deyil)
- Performance (lock yoxdur)
- Kod sadələşir (parameter passing lazım deyil)
Çatışmazlıqlar:
- Memory leak riski
- Thread pool-da xüsusi diqqət lazımdır
- Debugging çətin ola bilər
- Immutable objects üçün uyğun deyil