Коли і як слід використовувати змінну ThreadLocal?


876

Коли я повинен використовувати ThreadLocalзмінну?

Як він використовується?


11
Якщо ви використовуєте ThreadLocal, ніколи не пишіть обгортку над ним !!! Кожен розробник, який хоче використовувати цю змінну, повинен знати, що це "ThreadLocal"
Не помилка

2
@Notabug Якщо ви явні, ви можете назвати свою змінну, щоб ви мали справу з значенням ThreadLocal, наприклад RequestUtils.getCurrentRequestThreadLocal(). Не кажучи, що це дуже елегантно, але це пов'язано з тим, що ThreadLocal сама по собі не дуже елегантна в більшості випадків.
вир

@Sasha Чи можна ThreadLocal використовувати для зберігання сліду виконання
gaurav

Відповіді:


864

Одне можливе (і поширене) використання - коли у вас є якийсь об'єкт, який не є безпечним для потоків, але ви хочете уникнути синхронізації доступу до цього об’єкта (я дивлюся на вас, SimpleDateFormat ). Натомість дайте кожному потоку власний екземпляр об’єкта.

Наприклад:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Документація .


160
Ще одна альтернатива синхронізації або потоковому локальному - це зробити змінну локальною змінною. Місцеві варі завжди безпечні для потоків. Я припускаю, що дату DateFormats вважати поганою практикою, оскільки їх дорого створити, але я ніколи не бачив суцільних показників на цю тему.
Жульєн Частанг

18
Це висока ціна, яку потрібно заплатити за злом SimpleDateFormat. Можливо, краще скористатись безпечною для ниток альтернативою . Якщо ви згодні, що одинаки погані, то ThreadLocalце ще гірше.
Олександр Рижов

4
@overthink Чи є якась причина оголосити ThreadLocal статичною і остаточною, я маю на увазі продуктивність чи щось таке?
Сачин Гораде

4
Метод ThreadLocal.get () викличе ThreadLocal.initialValue () (один раз) для кожного потоку, це означає, що для кожного потоку створюється об’єкт SimpleDateFormat. Хіба не краще просто мати SimpleDateFormat як локальні змінні (оскільки нам не доведеться мати справу зі сміттям)?
sudeepdino008

3
Будьте обережні, щоб не використовувати подвійну дужку ініціалізації для вашого SimpleDateFormat, оскільки це створило б анонімний клас, щоб ваш завантажувач класів не міг збирати сміття. витік пам'яті: поверніть новий SimpleDateFormat () {{applyPattern ("yyyyMMdd HHmm")}};
користувач1944408

427

Оскільки a ThreadLocal- це посилання на дані всередині даної задачі Thread, ви можете закінчити витоки завантаження класу під час використання ThreadLocals на серверах прикладних програм, використовуючи потоки пулів. Ви повинні бути дуже обережні , про очищення будь-яких ThreadLocalS вас get()або set()за допомогою ThreadLocal«s remove()методу.

Якщо ви не очистите, коли закінчите, будь-які посилання на класи, завантажені як частина розгорнутої веб-сторінки, залишаться в постійній купі і ніколи не збиратимуть сміття. Повторне розгортання / повторне розгортання webapp не очистить Threadпосилання кожного на ваш клас (и) webapp, оскільки Threadце не те, що належить вашому webapp. Кожне наступне розгортання створить новий екземпляр класу, який ніколи не буде збирати сміття.

У вас виявляться винятки з пам'яті, які виникла через java.lang.OutOfMemoryError: PermGen spaceі після того, як деякий googling, ймовірно, просто збільшиться -XX:MaxPermSizeзамість виправлення помилки.

Якщо у вас виникають ці проблеми, ви можете визначити, який потік і клас зберігає ці посилання, використовуючи аналізатор пам’яті Eclipse та / або дотримуючись посібника та подальшого використання Френка Кієві .

Оновлення: знову відкрив запис у блозі Алекса Васеура, який допоміг мені віднайти деякі ThreadLocalпроблеми, які у мене виникли.


11
Алекс Вассер перемістив свій блог. Ось поточне посилання на статтю про витік пам'яті.
Кенстер

12
Здається, нитка, на яку посилається Жульєн, перейшла сюди , і її варто прочитати…
Дональні стипендії

28
Це надзвичайно багато підстав для відповіді, які, хоча й інформативні, жодним чином насправді не відповідають на питання.
Робін

8
Тепер, коли PermGen убитий Java 8, чи це змінює цю відповідь будь-яким чином?
Зла пральна машина

13
@Robin: Я не згоден. Питання полягає в тому, як правильно використовувати ThreadLocal, щоб отримати повне розуміння будь-якої концепції (ThreadLocal тут), також важливо зрозуміти, як не використовувати її та ризики недбалого використання, і саме в цьому відповідь Філа. Я радий, що він охопив ту точку, яку не висвітлено в інших відповідях. Усі ці голоси заслужені. Я вважаю, що ТА повинен будувати розуміння понять, а не бути лише сайтом якості.
Саурабх Патіль

166

Багато фреймворків використовують ThreadLocals для підтримки деякого контексту, пов'язаного з поточним потоком. Наприклад, коли поточна транзакція зберігається в ThreadLocal, вам не потрібно передавати її як параметр через кожен виклик методу, якщо хтось із стека потребує доступу до нього. Веб-програми можуть зберігати інформацію про поточний запит та сеанс у ThreadLocal, щоб програма мала до них доступ. З Guice ви можете використовувати ThreadLocals при реалізації користувальницьких приціли для інжектованих об'єктів ( по замовчуванням Guice в сервлет SCOPES , швидше за все , використовувати їх, а).

ThreadLocals - це різновид глобальних змінних (хоча трохи менше зла, оскільки вони обмежені однією ниткою), тому вам слід бути обережними при їх використанні, щоб уникнути небажаних побічних ефектів та витоку пам'яті. Створіть свої API так, що значення ThreadLocal завжди будуть автоматично очищені, коли вони більше не потрібні, і що неправильне використання API не буде можливим (наприклад, як це ). ThreadLocals можна використовувати для того, щоб зробити код більш чистим, а в деяких рідкісних випадках вони є єдиним способом змусити щось працювати (мій поточний проект мав два такі випадки; вони задокументовані тут у розділі "Статичні поля та глобальні змінні").


4
Саме так рамка exPOJO (www.expojo.com) дозволяє отримати доступ до ORM Session / PersistenceManager, не потребуючи накладних анотацій та ін'єкцій. Це на зразок "введення нитки" замість "введення об'єкта". Він забезпечує доступ до залежностей без необхідності (і накладних витрат) їх вбудовування в кожен об'єкт, який може потребувати цих залежностей. Дивовижно, як "легка вага" ви можете зробити рамку DI, коли використовуєте інжекцію різьби замість класичного DI (наприклад, Spring тощо)
Volksman

27
Чому мені довелося прокручувати вниз так далеко, щоб знайти цю важливу відповідь !?
Джеремі Штейн

1
@Esko, У вашому проекті використання ThreadLocal у хеш-коді та рівності не зайве. Кращий підхід - створити або передати посилання на функцію хеш-коду / рівняння, коли це потрібно. Таким чином, ви можете повністю уникнути необхідності злому ThreadLocal.
Pacerier


1
Це найкраще пояснення використання ТЛ.
Декстер

52

У Java, якщо у вас є дана, яка може змінюватись на кожну нитку, ваш вибір полягає в тому, щоб передати цю дату навколо кожному методу, який потребує (або може знадобитися) її, або пов'язати дату з потоком. Передача даної інформації всюди може бути працездатною, якщо всі ваші методи вже потребують обходу загальної змінної "контексту".

Якщо це не так, можливо, ви не хочете захаращувати підписи методу додатковим параметром. У світі без потоку ви можете вирішити проблему з еквівалентом Java глобальної змінної. У потоковому слові еквівалент глобальної змінної є локальною змінною.


13
Тож вам слід уникати місцевих жителів так само, як і глобальних. Я не можу прийняти, що це нормально для створення глобалів (локальних потоків) замість передачі значень навколо, людям це не подобається, оскільки це часто виявляє проблеми архітектури, які вони не хочуть виправляти.
Хуан Мендес

10
Можливо ... Це може бути корисно, хоча у вас є величезна існуюча база коду, до якої ви повинні додати нову дату, яку потрібно пройти всюди , наприклад, сеанс контексту, db транзакції, користувач, який увійшов у систему тощо .
Корнел Массон

6
Kudos для використання даних замість даних.
глибокий

Це мене викликало цікавість. 'datum' is the singular form and 'data' is the plural form.
Сіддхартха

23

Є дуже хороший приклад у книзі Java Concurrency in Practice . Там, де автор ( Джошуа Блох ) пояснює, як обмеження теми є одним з найпростіших способів досягнення безпеки потоку, і ThreadLocal є більш офіційним засобом підтримання конфігурації ниток. Врешті-решт він також пояснює, як люди можуть зловживати ним, використовуючи його як глобальні змінні.

Я скопіював текст із згаданої книги, але код 3.10 відсутній, тому що не дуже важливо зрозуміти, де слід використовувати ThreadLocal.

Локальні змінні нитки часто використовуються для запобігання спільному використанню в проектах, заснованих на змінних Singletons або глобальних змінних. Наприклад, однопотокова програма може підтримувати глобальне з'єднання з базою даних, яке ініціалізується при запуску, щоб уникнути необхідності передачі з'єднання кожному методу. Оскільки з'єднання JDBC може не бути безпечними для потоків, багатопотокове додаток, яке використовує глобальне з'єднання без додаткової координації, також не є безпечним для потоків. Використовуючи ThreadLocal для зберігання з'єднання JDBC, як у ConnectionHolder у Лістингу 3.10, кожен потік матиме власне з'єднання.

ThreadLocal широко застосовується у впровадженні рамок додатків. Наприклад, контейнери J2EE асоціюють контекст транзакції з виконавчим потоком протягом тривалості виклику EJB. Це легко реалізується за допомогою статичної Thread-Local, що містить контекст транзакції: коли рамковий код повинен визначати, яка транзакція виконується в даний час, він вибирає контекст транзакції з цього ThreadLocal. Це зручно тим, що він зменшує необхідність передавати інформацію про контекст виконання у кожен метод, але з'єднує будь-який код, який використовує цей механізм в рамках.

Легко зловживати ThreadLocal, трактуючи його властивість конфігурації потоків як ліцензію на використання глобальних змінних або як засіб створення аргументів «прихованого» методу. Як і глобальні змінні, потокові локальні змінні можуть зашкодити повторному використанню та ввести приховані муфти серед класів, і тому їх слід обережно використовувати.


18

По суті, коли вам потрібно , щоб значення змінної залежало від поточного потоку, і вам не зручно приєднувати значення до потоку якимось іншим способом (наприклад, потоком підкласифікації).

Типовий випадок, коли якийсь інший фреймворк створив нитку, в якій працює ваш код, наприклад, контейнер сервлетів, або де просто доцільніше використовувати ThreadLocal, тому що ваша змінна потім знаходиться "в своєму логічному місці" (а не змінній висить з підкласу "Нитка" або на іншій хеш-карті).

На своєму веб-сайті я маю ще кілька обговорень та прикладів, коли використовувати ThreadLocal, що також може представляти інтерес.

Деякі люди пропонують використовувати ThreadLocal як спосіб приєднати "ідентифікатор потоку" до кожного потоку в певних паралельних алгоритмах, де вам потрібен номер потоку (див., Наприклад, Herlihy & Shavit). У таких випадках перевірте, чи справді ви отримуєте вигоду!


Чи можна ThreadLocal використовувати для зберігання сліду виконання
gaurav

13
  1. ThreadLocal на Java був введений на JDK 1.2, але згодом був утворений в JDK 1.5 для введення безпеки типу для змінної ThreadLocal.

  2. ThreadLocal може бути пов'язаний із областю Thread, весь код, який виконується Thread, має доступ до змінних ThreadLocal, але два потоки не можуть бачити змінну ThreadLocal.

  3. Кожен потік містить ексклюзивну копію змінної ThreadLocal, яка стає придатною до збору сміття після закінчення або відмирання потоку, як правило, або за рахунок будь-якого винятку. Зважаючи на те, що змінна ThreadLocal не має інших активних посилань.

  4. Змінні ThreadLocal на Java, як правило, є приватними статичними полями в Класах і підтримують його стан всередині Thread.

Детальніше: ThreadLocal на Java - приклад програми та навчального посібника


Чи можна ThreadLocal використовувати для зберігання сліду виконання
gaurav

11

Документація говорить про це дуже добре: "кожен потік, який має доступ до [локальної змінної потоку] (через метод get або set), має власну, незалежно ініціалізовану копію змінної".

Ви використовуєте його, коли кожна нитка повинна мати свою копію чогось. За замовчуванням дані діляться між потоками.


18
За замовчуванням статичні об'єкти або об'єкти, явно передані між потоками, де обидва потоки мають посилання на один і той же об'єкт, поділяються. Об'єкти, оголошені локально в потоці, не поділяються (вони локальні для стека потоку). Просто хотів уточнити це.
Ян Варлі

@IanVarley: Дякую за вказівку на це. Отже, якщо у нас є змінна, яка оголошена і використовується в класі MyThread, то чи потрібна нам ThreadLocal у такому випадку? Приклад: клас MyThread розширює нитку {String var1 ;, int var2; } в цьому випадку var1 і var2 будуть частиною власного стека Thread і не поділяються, тож чи потрібен нам ThreadLocal у цьому випадку? Я можу бути абсолютно невірним у своєму розумінні, будь ласка, керівництво.
Jayesh

4
Якщо кожен потік хоче мати свою копію чогось, чому його не можна просто оголосити локальним (що завжди безпечно для потоку)?
sudeepdino008

@ sudeepdino008 ви не завжди маєте контроль над створенням цих тем (веб-фреймворки, бібліотеки потоків)
JMess

10

Сервер Webapp може зберігати пул потоків, а ThreadLocalперед відповіддю клієнту потрібно видалити var, тому поточний потік може бути повторно використаний наступним запитом.


8

Два випадки використання, коли може використовуватися змінна потокова локалізація -
1- Коли у нас є вимога асоціювати стан з потоком (наприклад, ідентифікатор користувача або ідентифікатор транзакції). Зазвичай це трапляється з веб-додатком, коли кожен запит, що надходить до сервлета, має з ним пов'язаний унікальний ID транзакції.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

Зауважимо, що тут метод withInitial реалізований за допомогою лямбда-експресії.
2 - Ще один випадок використання, коли ми хочемо мати безпечний екземпляр для потоку, і ми не хочемо використовувати синхронізацію, оскільки вартість продуктивності при синхронізації більше. Один з таких випадків, коли використовується SimpleDateFormat. Оскільки SimpleDateFormat не є безпечним для потоків, тому ми повинні створити механізм, щоб зробити його потоком безпечним.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

Я все ще не бачив переваги використання ThreadLocal для створення унікального ідентифікатора в прикладі одного, якщо ваша мета - повернути додаткове унікальне ціле значення. `` `public class ThreadId {// Атомне ціле число, що містить наступний ідентифікатор потоку для присвоєння приватного статичного кінцевого AtomicInteger nextId = новий AtomicInteger (0); // Повертає унікальний ідентифікатор поточного потоку, призначаючи його за необхідності загальнодоступним статичним int get () {return nextId..getAndIncrement (); }} `` `
dashenswen

7

З моменту випуску Java 8 існує декларативніший спосіб ініціалізації ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

До випуску Java 8 вам потрібно було зробити наступне:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

Більше того, якщо метод інстанції (конструктор, заводський метод) класу, для якого використовується ThreadLocal, не приймає жодних параметрів, ви можете просто використовувати посилання методу (введені в Java 8):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Примітка. Оцінка лінива, оскільки ви передаєте java.util.function.Supplierлямбда, яка оцінюється лише тоді, коли ThreadLocal#getвикликається, але значення раніше не оцінювалося.


5

Ви повинні бути дуже обережними з малюнком ThreadLocal. Існують деякі основні сторони, як згадував Філ, але одна, про яку не згадувалося, полягає у тому, щоб переконатися, що код, що встановлює контекст ThreadLocal, не є "повторним".

Погані речі можуть трапитися, коли код, який встановлює інформацію, запускається другий чи третій раз, оскільки інформація у вашій потоці може почати мутувати, коли ви цього не очікували. Тож подбайте про те, щоб інформація ThreadLocal не була встановлена ​​перед тим, як її знову встановити.


2
Повторне вступництво не є проблемою, якщо код готовий боротися з ним. При введенні запишіть, чи змінна вже встановлена, а при виході відновіть її попереднє значення (якщо вона була), або видаліть її (якщо ні).
supercat

1
@Jeff, чувак, це стосується кожного написаного вами коду, а не лише ThreadLocalшаблону. Якщо ви F(){ member=random(); F2(); write(member); }і F2 заміняєте члена на нове значення, то очевидно write(member)більше не буде писати число, яке ви random()змінили. Це буквально просто здоровий глузд. Точно так само, якщо ви робите F(){ F(); }, тоді вам удача з нескінченним циклом! Це справедливо скрізь і не є специфічним для ThreadLocal.
Pacerier

@Jeff, приклад коду?
Pacerier

5

коли?

Коли об'єкт не є безпечним для потоків, замість синхронізації, яка гальмує масштабованість, надайте по одному об’єкту кожному потоку та збережіть його область застосування потоку, яка є ThreadLocal. Одними з найбільш часто використовуваних, але не безпечних для потоків об'єктів є база даних Connection та JMSConnection.

Як?

Одним із прикладів є те, що Spring Framework використовує ThreadLocal для управління транзакціями за кадром, зберігаючи ці об'єкти з'єднання у змінних ThreadLocal. На високому рівні, коли транзакція запускається, вона отримує з'єднання (і відключає автоматичну фіксацію) і зберігає її в ThreadLocal. при подальших викликах db він використовує те саме з'єднання для зв'язку з db. Зрештою, він бере з'єднання з ThreadLocal і здійснює (або відкочує) транзакцію та звільняє з'єднання.

Я думаю, що log4j також використовує ThreadLocal для підтримки MDC.


5

ThreadLocal корисно, коли ви хочете мати певний стан, який не слід ділити між різними потоками, але він повинен бути доступний з кожної нитки протягом усього життя.

Як приклад, уявіть веб-додаток, де кожен запит подається різним потоком. Уявіть, що для кожного запиту вам потрібен фрагмент даних кілька разів, що обчислити досить дорого. Однак ці дані можуть бути змінені для кожного вхідного запиту, що означає, що ви не можете використовувати звичайний кеш. Простим, швидким рішенням цієї проблеми було б мати ThreadLocalзмінний доступ до цих даних, так що вам потрібно буде обчислити їх лише один раз для кожного запиту. Звичайно, цю проблему можна вирішити і без використання ThreadLocal, але я її придумав для ілюстрації.

Зважаючи на це, майте на увазі, що ThreadLocals по суті є формою глобальної держави. Як результат, це має багато інших наслідків і його слід використовувати лише після розгляду всіх інших можливих рішень.


Місцеві місцеположення НЕ є глобальним станом, якщо ви не зробите це глобальним станом. Вони практично завжди доступні стековим ресурсом. Ви можете імітувати локальний потік, просто пройшовши цю змінну у всіх своїх методах (що затримується); це не робить його глобальним станом ...
Enerccio

Це форма глобального стану, в тому сенсі, що вона доступна з будь-якої точки коду (у контексті тієї ж нитки). Це пов'язане з усіма наслідками, такими як неможливість міркувати про те, хто читає та пише це значення. Використання функціонального параметра не є затримкою, це хороша практика просування чистих інтерфейсів. Однак я погоджуюся з вами, що передача параметра через всю глибину вашої кодової бази є запахом коду. Але я також вважаю, що в багатьох випадках використання ThreadLocal - це кодовий запах, в першу чергу, який привів вас сюди, тому це, на що варто переглянути.
Дімос

Він просто може бути частиною стану об'єкта, не повинен використовуватися в усьому світі. Звичайно, ви отримуєте трохи накладні витрати, але якщо декілька об'єктів мають різний стан на потоку, ви можете використовувати ThreadLocalяк поле об’єкта ...
Enerccio

Ти правий. Я, напевно, натрапив на кілька зловживань ThreadLocal, де це було доступно в усьому світі. Як ви вже говорили, воно все ще може використовуватися як поле класу, що обмежує видимість.
Дімос

4

Тут немає нічого нового, але я виявив, що ThreadLocalдуже корисно при використанні Bean Validation у веб-додатку. Повідомлення про валідацію локалізуються, але за замовчуванням Locale.getDefault(). Ви можете налаштувати Validatorінший MessageInterpolator, але немає способу вказувати Localeчас дзвінка validate. Таким чином, ви можете створити статичний ThreadLocal<Locale>(а ще краще, загальний контейнер з іншими речами, які вам можуть знадобитися, ThreadLocalа потім мати свій власний MessageInterpolatorвибір Localeіз цього. Наступним кроком є ​​написання запису, ServletFilterякий використовує значення сеансу, або request.getLocale()вибір локалу та збереження це у вашому ThreadLocalдовіднику.


4

Як згадував @unknown (google), його використання полягає у визначенні глобальної змінної, в якій значення, на яке посилається, може бути унікальним у кожному потоці. Це звичайно тягне за собою збереження якоїсь контекстної інформації, яка пов'язана з поточним потоком виконання.

Ми використовуємо його в середовищі Java EE для передачі ідентичності користувача класам, які не відомі Java EE (не мають доступу до HttpSession або EJB SessionContext). Таким чином код, який використовує ідентичність для операцій, заснованих на захисті, може отримати доступ до ідентичності з будь-якого місця, без необхідності явно передавати його в кожному виклику методу.

Цикл запитів / відповідей операцій у більшості дзвінків Java EE полегшує цей тип використання, оскільки він дає чітко визначені точки входу та виходу для встановлення та зняття ThreadLocal.


4

ThreadLocal забезпечить доступ до об'єкта, що змінюється, декількома потоками в не синхронізованому методі синхронізується, означає зробити об'єкт, що змінюється, незмінним у межах методу.

Це досягається, надаючи новий екземпляр змінного об'єкта для кожного потоку, спробуйте отримати доступ до нього. Отже, це локальна копія до кожної нитки. Це деякий хак для створення змінної екземпляра в методі, до якого можна отримати доступ як локальна змінна. Як ви знаєте метод локальна змінна доступна лише для потоку, одна різниця полягає; Локальні змінні методу не будуть доступні для потоку, коли виконання методу закінчиться, коли мінливий об'єкт, спільний з потоком мовлення, буде доступний у кількох методах, поки ми його не очистимо.

За визначенням:

Клас ThreadLocal в Java дозволяє створювати змінні, які можна читати і записувати лише з одного потоку. Таким чином, навіть якщо два потоки виконують один і той же код, і в коді є посилання на змінну ThreadLocal, два потоки не можуть бачити змінні ThreadLocal один одного.

Кожен Threadв java містить ThreadLocalMapу ньому.
Де

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

Досягнення локальної теми:

Тепер створіть клас обгортки для ThreadLocal, який збиратиметься об'єктом, що змінюється, як нижче (з або без initialValue()).
Тепер геттер і сетер цієї обгортки працюватимуть над потоковим екземпляром замість змінного об'єкта.

Якщо getter () ниткових локальних даних не знайшов жодного значення у в потоковій мапі Thread; тоді він викличе InitiValue (), щоб отримати його приватну копію стосовно потоку.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

Тепер wrapper.getDateFormatter()зателефонуємо threadlocal.get()і перевіримо, чи currentThread.threadLocalMapмістить цей (потоковий) екземпляр.
Якщо так, поверніть значення (SimpleDateFormat) для відповідного екземпляра потокової локалізації,
інше додайте карту з цим екземпляром потокової локалізації, InitiValue ().

Цим забезпечується безпека ниток для цього класу, що змінюється; кожен потік працює зі своїм змінним екземпляром, але з тим самим екземпляром ThreadLocal. Значить Всі потоки матимуть один і той же екземпляр ThreadLocal як ключ, але різні екземпляри SimpleDateFormat як значення.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java


3

Локальні змінні нитки часто використовуються для запобігання спільному використанню в проектах, заснованих на змінних Singletons або глобальних змінних.

Його можна використовувати в таких сценаріях, як встановлення окремого з'єднання JDBC для кожного потоку, коли ви не використовуєте пул з'єднань.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

Коли ви зателефонуєте на getConnection, він поверне з'єднання, пов'язане з цією потоком. Те ж саме можна зробити з іншими властивостями, такими як формат дати, контекст транзакції, який ви не хочете ділити між потоками.

Ви могли також використовувати локальні змінні для одних і тих же, але зазвичай цей ресурс займає час у створенні, тому ви не хочете створювати їх знову і знову, коли виконуєте з ними якусь ділову логіку. Однак значення ThreadLocal зберігаються в самому об'єкті потоку, і як тільки нитка збирається сміття, ці значення також втрачаються.

Це посилання дуже добре пояснює використання ThreadLocal.


2
У цьому прикладі головне питання: хто несе відповідальність за закриття з'єднання? Замість того, щоб створювати це з'єднання як значення ініціалі, дозвольте споживачеві з'єднання створити з'єднання явно і прив’язати його до потоку через ThreadLocal. Творець з'єднання також відповідає за закриття. Це не ясно в цьому прикладі. Створення та прив'язка з'єднання також може бути прихована у простих рамках чимось на зразок Transaction.begin () та Transaction.end ().
Рік-Райнер Людвіг

2

Клас ThreadLocal в Java дозволяє створювати змінні, які можна читати і записувати лише з одного потоку. Таким чином, навіть якщо два потоки виконують один і той же код, і в коді є посилання на змінну ThreadLocal, два потоки не можуть бачити змінні ThreadLocal один одного.

Детальніше


2

Існує 3 сценарії використання помічника класу, як SimpleDateFormat у багатопотоковому коді, найкращим з яких є використання ThreadLocal

Сценарії

1- Використання подібного об'єкта для обміну за допомогою блокування або механізму синхронізації, що робить додаток повільним

2- Використання як локального об'єкта всередині методу

У цьому випадку, якщо у нас є 4 потоки, кожен з яких викликає метод 1000 разів, тоді у нас створено
4000 об’єктів SimpleDateFormat і чекає, коли GC видалить їх

3- Використання теми ThreadLocal

якщо у нас є 4 потоки, і ми дали кожному потоку один екземпляр SimpleDateFormat,
тому у нас є 4 потоки , 4 об'єкти SimpleDateFormat.

Немає необхідності механізму блокування та створення та знищення об'єктів. (Складність часу та просторова складність)


2

[Для довідки] ThreadLocal не може вирішити проблеми оновлення спільного об’єкта. Рекомендується використовувати об'єкт staticThreadLocal, який розділяється всіма операціями в одному потоці. Метод [Обов’язковий] delete () повинен бути реалізований змінними ThreadLocal, особливо при використанні пулів потоків, в яких потоки часто використовуються повторно. В іншому випадку це може вплинути на подальшу логіку бізнесу та спричинити несподівані проблеми, такі як витік пам'яті.


1

Кешуючи, колись вам доведеться обчислити однакове значення багато часу, тому зберігаючи останній набір входів для методу, і результат ви можете пришвидшити код. Використовуючи локальне зберігання теми, вам не доведеться думати про блокування.


1

ThreadLocal - це спеціально передбачена функціональність JVM для забезпечення ізольованого місця для зберігання лише для потоків. як значення зміненої області змінної примірника пов'язані лише з певним екземпляром класу. кожен об'єкт має свої єдині значення, і вони не можуть бачити значення один одному. так само поняття змінних ThreadLocal, вони локальні для потоку в сенсі об'єктних примірників, інший потік, крім того, який створив його, не може бачити. Дивіться тут

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

1

Threadlocal забезпечує дуже простий спосіб досягти повторного використання об'єктів з нульовою вартістю.

У мене була ситуація, коли кілька потоків створювали зображення кеша, що змінюється, у кожному повідомленні про оновлення.

Я використовував Threadlocal у кожному потоці, а потім кожну нитку потрібно було б просто скинути старе зображення, а потім оновити його знову з кеша кожного повідомлення про оновлення.

Звичайні об'єкти для багаторазового використання з пулів об'єктів мають пов'язані з ними витрати на безпеку потоків, тоді як такий підхід не має.


0

Спробуйте цей невеликий приклад, щоб відчути змінну ThreadLocal:

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


Виведення консолі, якщо спочатку виконано нитку BookA:
Результат BookA: 'wordA1, wordA2, wordA3'.
Книга результатівB: 'wordB1, wordB2'.

Виведення консолі, якщо спочатку виконано нитку BookB:
Результат BookB: 'wordB1, wordB2'.
Результат BookA: 'wordA1, wordA2, wordA3'.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.