Локальная переменная потока - это переменная типа ThreadLocal. Каждый поток, который обращается к локальной переменной потока, имеет свою собственную, независимо инициализированную копию переменной. Чтобы записать или прочитать значение локальной переменной потока, вызовите ее метод set или get соответственно. Обычно локальная переменная потока объявляется как final static поле, чтобы многие компоненты могли легко получить к ней доступ.
В следующем примере класс TLDBConn представляет подключение к базе данных. TLBDBConn::open Метод выводит строку и имя пользователя. Класс TLServer представляет саму базу данных. Он содержит один метод, TLServer::fetchOrder который возвращает строку, содержащую имя пользователя. Класс TLApplication создает несколько TLDBConn объектов, каждый из которых создается другим пользователем и каждый в своем собственном потоке. TLApplication::testConnection Случайным образом изменяет продолжительность потока, чтобы у потоков была возможность выполняться одновременно.
Рисунок 14-6 User.java
Копироватьpublic class User {
public String name;
public User(String n) {
name = n;
}
}
Рисунок 14-7 TLDBConn.java
Копироватьpublic class TLDBConn {
final static ThreadLocal<User> TLUSER = new ThreadLocal<>();
public static String open(String info) {
System.out.println(info + ": " + TLUSER.get().name);
return info + ": " + TLUSER.get().name;
}
}
Figure 14-8 TLServer.java
Copypublic class TLServer {
public static String fetchOrder() {
return "Fetching order for " + TLDBConn.TLUSER.get().name;
}
}
Рисунок 14-9 TLApplication.java
Копироватьimport java.util.*;
public class TLApplication {
public void testConnection(User u) {
Runnable r = () -> {
TLDBConn.TLUSER.set(u);
TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
System.out.println(TLServer.fetchOrder());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
TLDBConn.TLUSER.set(new User(u.name + " renamed"));
TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
};
Thread t = new Thread(r, u.name);
t.start();
}
public static void main(String[] args) {
TLApplication myApp = new TLApplication();
for(int i=0 ; i<5; i++) {
myApp.testConnection(new User("user" + i));
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
TLApplication выводит результат, аналогичный следующему:
КопироватьThread user0, testConnection: user0
Fetching order for user0
Thread user1, testConnection: user1
Fetching order for user1
Thread user2, testConnection: user2
Fetching order for user2
Thread user0, testConnection: user0 renamed
Thread user1, testConnection: user1 renamed
Thread user2, testConnection: user2 renamed
Thread user3, testConnection: user3
Fetching order for user3
Thread user3, testConnection: user3 renamed
Thread user4, testConnection: user4
Fetching order for user4
Thread user4, testConnection: user4 renamed
Обратите внимание, что, хотя переменная-член TLDBConn.USER объявлена как final static, ее значение уникально для каждого потока, созданного с помощью TLApplication.
Также обратите внимание, что у TLServer::fetchOrder метода нет параметров. В частности, для передачи ему TLApplication::testConnection параметра не требуется код. User TLServer::fetchOrder может напрямую обращаться к TLDBConn.USER локальной переменной потока, которая соответствует потоку, в котором она выполняется:
Копироватьreturn "Fetching order for " + TLDBConn.TLUSER.get().name;
Следовательно, локальные переменные потока позволяют вам скрывать аргументы метода.
Наследование потока-локальные переменные
Когда родительский поток запускает дочерний поток, ни одно из значений локальных переменных родительского потока не наследуется дочерним потоком. Однако, если вы хотите, чтобы дочерний поток наследовал значения локальных значений родительского потока, создайте локальную переменную потока с InheritableThreadLocal классом вместо этого.
Следующий пример включает в себя InheritableThreadLocal переменную с именем TLADMIN в дополнение к ThreadLocal named TLUSER:
Рисунок 14-10 TLDBConn.java
Копироватьpublic class TLDBConn {
final static ThreadLocal<User> TLUSER = new ThreadLocal<>();
final static InheritableThreadLocal<User> TLADMIN = new InheritableThreadLocal<>();
public static String open(String info) {
System.out.println(info + ": " + TLUSER.get().name);
return info + ": " + TLUSER.get().name;
}
}
Следующий метод запускает поток с именем childThread внутри потока. Поток childThread извлекает значение InheritableThreadLocal переменной с именем TLADMINи пытается получить значение ThreadLocal переменной с именем TLUSER:
Copypublic void testConnectionWithInheritableTL(User u) {
Runnable r = () -> {
TLDBConn.TLUSER.set(u);
TLDBConn.TLADMIN.set(new User("Admin"));
TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
System.out.println(TLServer.fetchOrder());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread childThread = new Thread(
() -> {
System.out.println("Child thread");
System.out.println("TLADMIN: " + TLDBConn.TLADMIN.get().name);
try {
System.out.println("TLUSER: " + TLDBConn.TLUSER.get().name);
} catch (NullPointerException e) {
System.out.println("NullPointerException: TLUSER hasn't beet set");
}
}
);
childThread.start();
TLDBConn.TLUSER.set(new User(u.name + " renamed"));
TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
};
Thread t = new Thread(r, u.name);
t.start();
}
При вызове этого метода следующий оператор в экземпляре childThread выдает NullPointerException:
КопироватьSystem.out.println("TLUSER: " + TLDBConn.TLUSER.get().name);
Значение ThreadLocal переменной TLUSERне было унаследовано childThread. Однако значение InheritableThreadLocal переменной TLADMIN было унаследовано childThread. Когда childThread запускается, выводится следующий вывод:
КопироватьChild thread
TLADMIN: Admin
NullPointerException: TLUSER hasn't beet set
Проблемы с локальными переменными потока Переменные
К сожалению, локальные переменные потока имеют некоторые конструктивные недостатки.
Значения с ограниченной областью действия могут решить эти проблемы с помощью локальных переменных потока.Примечание:
Неограниченная изменчивость
Каждая локальная переменная потока изменяема. Это может затруднить определение в коде вашего приложения, какие компоненты обновляют общее состояние и в каком порядке. В примере, описанном в разделе Локальные переменные потока, TLApplication::testConnection переназначает TLDBConn.TLUSER новое значение:
КопироватьRunnable r = () -> {
TLDBConn.TLUSER.set(u);
TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
System.out.println(TLServer.fetchOrder());
// ...
TLDBConn.TLUSER.set(new User(u.name + " renamed")); TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
};
Неограничен Срок службы
Среда выполнения Java сохраняет потоковое воплощение локальной переменной потока в течение всего времени существования потока или до тех пор, пока код в потоке не вызовет метод локальной переменной потока remove. Если вы не вызовете этот метод, среда выполнения Java может сохранять данные потока дольше, чем необходимо. Если вы используете пул потоков, то значение локальной переменной потока, заданное в одной задаче, может просочиться в другую задачу. Если вы несколько раз устанавливали значение локальной переменной потока в потоке, то может быть неясно, когда для потока безопасно вызывать remove метод, что может вызвать долговременную утечку памяти.
Дорогостоящее наследование
Накладные расходы на локальные переменные потока могут быть хуже при использовании большого количества потоков, поскольку локальные переменные родительского потока могут наследоваться дочерними потоками.