Əsas məzmuna keçin

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

  1. Həmişə remove() çağırın:

    try {
    threadLocal.set(value);
    // work
    } finally {
    threadLocal.remove(); // Mütləq
    }
  2. 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!
    }
    });
  3. Static olaraq declare edin:

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  4. 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