Коли я повинен використовувати ThreadLocal
змінну?
Як він використовується?
RequestUtils.getCurrentRequestThreadLocal()
. Не кажучи, що це дуже елегантно, але це пов'язано з тим, що ThreadLocal сама по собі не дуже елегантна в більшості випадків.
Коли я повинен використовувати ThreadLocal
змінну?
Як він використовується?
RequestUtils.getCurrentRequestThreadLocal()
. Не кажучи, що це дуже елегантно, але це пов'язано з тим, що ThreadLocal сама по собі не дуже елегантна в більшості випадків.
Відповіді:
Одне можливе (і поширене) використання - коли у вас є якийсь об'єкт, який не є безпечним для потоків, але ви хочете уникнути синхронізації доступу до цього об’єкта (я дивлюся на вас, 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);
}
}
SimpleDateFormat
. Можливо, краще скористатись безпечною для ниток альтернативою . Якщо ви згодні, що одинаки погані, то ThreadLocal
це ще гірше.
Оскільки a ThreadLocal
- це посилання на дані всередині даної задачі Thread
, ви можете закінчити витоки завантаження класу під час використання ThreadLocal
s на серверах прикладних програм, використовуючи потоки пулів. Ви повинні бути дуже обережні , про очищення будь-яких ThreadLocal
S вас get()
або set()
за допомогою ThreadLocal
«s remove()
методу.
Якщо ви не очистите, коли закінчите, будь-які посилання на класи, завантажені як частина розгорнутої веб-сторінки, залишаться в постійній купі і ніколи не збиратимуть сміття. Повторне розгортання / повторне розгортання webapp не очистить Thread
посилання кожного на ваш клас (и) webapp, оскільки Thread
це не те, що належить вашому webapp. Кожне наступне розгортання створить новий екземпляр класу, який ніколи не буде збирати сміття.
У вас виявляться винятки з пам'яті, які виникла через java.lang.OutOfMemoryError: PermGen space
і після того, як деякий googling, ймовірно, просто збільшиться -XX:MaxPermSize
замість виправлення помилки.
Якщо у вас виникають ці проблеми, ви можете визначити, який потік і клас зберігає ці посилання, використовуючи аналізатор пам’яті Eclipse та / або дотримуючись посібника та подальшого використання Френка Кієві .
Оновлення: знову відкрив запис у блозі Алекса Васеура, який допоміг мені віднайти деякі ThreadLocal
проблеми, які у мене виникли.
Багато фреймворків використовують ThreadLocals для підтримки деякого контексту, пов'язаного з поточним потоком. Наприклад, коли поточна транзакція зберігається в ThreadLocal, вам не потрібно передавати її як параметр через кожен виклик методу, якщо хтось із стека потребує доступу до нього. Веб-програми можуть зберігати інформацію про поточний запит та сеанс у ThreadLocal, щоб програма мала до них доступ. З Guice ви можете використовувати ThreadLocals при реалізації користувальницьких приціли для інжектованих об'єктів ( по замовчуванням Guice в сервлет SCOPES , швидше за все , використовувати їх, а).
ThreadLocals - це різновид глобальних змінних (хоча трохи менше зла, оскільки вони обмежені однією ниткою), тому вам слід бути обережними при їх використанні, щоб уникнути небажаних побічних ефектів та витоку пам'яті. Створіть свої API так, що значення ThreadLocal завжди будуть автоматично очищені, коли вони більше не потрібні, і що неправильне використання API не буде можливим (наприклад, як це ). ThreadLocals можна використовувати для того, щоб зробити код більш чистим, а в деяких рідкісних випадках вони є єдиним способом змусити щось працювати (мій поточний проект мав два такі випадки; вони задокументовані тут у розділі "Статичні поля та глобальні змінні").
У Java, якщо у вас є дана, яка може змінюватись на кожну нитку, ваш вибір полягає в тому, щоб передати цю дату навколо кожному методу, який потребує (або може знадобитися) її, або пов'язати дату з потоком. Передача даної інформації всюди може бути працездатною, якщо всі ваші методи вже потребують обходу загальної змінної "контексту".
Якщо це не так, можливо, ви не хочете захаращувати підписи методу додатковим параметром. У світі без потоку ви можете вирішити проблему з еквівалентом Java глобальної змінної. У потоковому слові еквівалент глобальної змінної є локальною змінною.
'datum' is the singular form and 'data' is the plural form.
Є дуже хороший приклад у книзі Java Concurrency in Practice . Там, де автор ( Джошуа Блох ) пояснює, як обмеження теми є одним з найпростіших способів досягнення безпеки потоку, і ThreadLocal є більш офіційним засобом підтримання конфігурації ниток. Врешті-решт він також пояснює, як люди можуть зловживати ним, використовуючи його як глобальні змінні.
Я скопіював текст із згаданої книги, але код 3.10 відсутній, тому що не дуже важливо зрозуміти, де слід використовувати ThreadLocal.
Локальні змінні нитки часто використовуються для запобігання спільному використанню в проектах, заснованих на змінних Singletons або глобальних змінних. Наприклад, однопотокова програма може підтримувати глобальне з'єднання з базою даних, яке ініціалізується при запуску, щоб уникнути необхідності передачі з'єднання кожному методу. Оскільки з'єднання JDBC може не бути безпечними для потоків, багатопотокове додаток, яке використовує глобальне з'єднання без додаткової координації, також не є безпечним для потоків. Використовуючи ThreadLocal для зберігання з'єднання JDBC, як у ConnectionHolder у Лістингу 3.10, кожен потік матиме власне з'єднання.
ThreadLocal широко застосовується у впровадженні рамок додатків. Наприклад, контейнери J2EE асоціюють контекст транзакції з виконавчим потоком протягом тривалості виклику EJB. Це легко реалізується за допомогою статичної Thread-Local, що містить контекст транзакції: коли рамковий код повинен визначати, яка транзакція виконується в даний час, він вибирає контекст транзакції з цього ThreadLocal. Це зручно тим, що він зменшує необхідність передавати інформацію про контекст виконання у кожен метод, але з'єднує будь-який код, який використовує цей механізм в рамках.
Легко зловживати ThreadLocal, трактуючи його властивість конфігурації потоків як ліцензію на використання глобальних змінних або як засіб створення аргументів «прихованого» методу. Як і глобальні змінні, потокові локальні змінні можуть зашкодити повторному використанню та ввести приховані муфти серед класів, і тому їх слід обережно використовувати.
По суті, коли вам потрібно , щоб значення змінної залежало від поточного потоку, і вам не зручно приєднувати значення до потоку якимось іншим способом (наприклад, потоком підкласифікації).
Типовий випадок, коли якийсь інший фреймворк створив нитку, в якій працює ваш код, наприклад, контейнер сервлетів, або де просто доцільніше використовувати ThreadLocal, тому що ваша змінна потім знаходиться "в своєму логічному місці" (а не змінній висить з підкласу "Нитка" або на іншій хеш-карті).
На своєму веб-сайті я маю ще кілька обговорень та прикладів, коли використовувати ThreadLocal, що також може представляти інтерес.
Деякі люди пропонують використовувати ThreadLocal як спосіб приєднати "ідентифікатор потоку" до кожного потоку в певних паралельних алгоритмах, де вам потрібен номер потоку (див., Наприклад, Herlihy & Shavit). У таких випадках перевірте, чи справді ви отримуєте вигоду!
ThreadLocal на Java був введений на JDK 1.2, але згодом був утворений в JDK 1.5 для введення безпеки типу для змінної ThreadLocal.
ThreadLocal може бути пов'язаний із областю Thread, весь код, який виконується Thread, має доступ до змінних ThreadLocal, але два потоки не можуть бачити змінну ThreadLocal.
Кожен потік містить ексклюзивну копію змінної ThreadLocal, яка стає придатною до збору сміття після закінчення або відмирання потоку, як правило, або за рахунок будь-якого винятку. Зважаючи на те, що змінна ThreadLocal не має інших активних посилань.
Змінні ThreadLocal на Java, як правило, є приватними статичними полями в Класах і підтримують його стан всередині Thread.
Детальніше: ThreadLocal на Java - приклад програми та навчального посібника
Документація говорить про це дуже добре: "кожен потік, який має доступ до [локальної змінної потоку] (через метод get або set), має власну, незалежно ініціалізовану копію змінної".
Ви використовуєте його, коли кожна нитка повинна мати свою копію чогось. За замовчуванням дані діляться між потоками.
Два випадки використання, коли може використовуватися змінна потокова локалізація -
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()));
}
}
З моменту випуску 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
викликається, але значення раніше не оцінювалося.
Ви повинні бути дуже обережними з малюнком ThreadLocal. Існують деякі основні сторони, як згадував Філ, але одна, про яку не згадувалося, полягає у тому, щоб переконатися, що код, що встановлює контекст ThreadLocal, не є "повторним".
Погані речі можуть трапитися, коли код, який встановлює інформацію, запускається другий чи третій раз, оскільки інформація у вашій потоці може почати мутувати, коли ви цього не очікували. Тож подбайте про те, щоб інформація ThreadLocal не була встановлена перед тим, як її знову встановити.
ThreadLocal
шаблону. Якщо ви F(){ member=random(); F2(); write(member); }
і F2 заміняєте члена на нове значення, то очевидно write(member)
більше не буде писати число, яке ви random()
змінили. Це буквально просто здоровий глузд. Точно так само, якщо ви робите F(){ F(); }
, тоді вам удача з нескінченним циклом! Це справедливо скрізь і не є специфічним для ThreadLocal
.
коли?
Коли об'єкт не є безпечним для потоків, замість синхронізації, яка гальмує масштабованість, надайте по одному об’єкту кожному потоку та збережіть його область застосування потоку, яка є ThreadLocal. Одними з найбільш часто використовуваних, але не безпечних для потоків об'єктів є база даних Connection та JMSConnection.
Як?
Одним із прикладів є те, що Spring Framework використовує ThreadLocal для управління транзакціями за кадром, зберігаючи ці об'єкти з'єднання у змінних ThreadLocal. На високому рівні, коли транзакція запускається, вона отримує з'єднання (і відключає автоматичну фіксацію) і зберігає її в ThreadLocal. при подальших викликах db він використовує те саме з'єднання для зв'язку з db. Зрештою, він бере з'єднання з ThreadLocal і здійснює (або відкочує) транзакцію та звільняє з'єднання.
Я думаю, що log4j також використовує ThreadLocal для підтримки MDC.
ThreadLocal
корисно, коли ви хочете мати певний стан, який не слід ділити між різними потоками, але він повинен бути доступний з кожної нитки протягом усього життя.
Як приклад, уявіть веб-додаток, де кожен запит подається різним потоком. Уявіть, що для кожного запиту вам потрібен фрагмент даних кілька разів, що обчислити досить дорого. Однак ці дані можуть бути змінені для кожного вхідного запиту, що означає, що ви не можете використовувати звичайний кеш. Простим, швидким рішенням цієї проблеми було б мати ThreadLocal
змінний доступ до цих даних, так що вам потрібно буде обчислити їх лише один раз для кожного запиту. Звичайно, цю проблему можна вирішити і без використання ThreadLocal
, але я її придумав для ілюстрації.
Зважаючи на це, майте на увазі, що ThreadLocal
s по суті є формою глобальної держави. Як результат, це має багато інших наслідків і його слід використовувати лише після розгляду всіх інших можливих рішень.
ThreadLocal
як поле об’єкта ...
ThreadLocal
, де це було доступно в усьому світі. Як ви вже говорили, воно все ще може використовуватися як поле класу, що обмежує видимість.
Тут немає нічого нового, але я виявив, що ThreadLocal
дуже корисно при використанні Bean Validation у веб-додатку. Повідомлення про валідацію локалізуються, але за замовчуванням Locale.getDefault()
. Ви можете налаштувати Validator
інший MessageInterpolator
, але немає способу вказувати Locale
час дзвінка validate
. Таким чином, ви можете створити статичний ThreadLocal<Locale>
(а ще краще, загальний контейнер з іншими речами, які вам можуть знадобитися, ThreadLocal
а потім мати свій власний MessageInterpolator
вибір Locale
із цього. Наступним кроком є написання запису, ServletFilter
який використовує значення сеансу, або request.getLocale()
вибір локалу та збереження це у вашому ThreadLocal
довіднику.
Як згадував @unknown (google), його використання полягає у визначенні глобальної змінної, в якій значення, на яке посилається, може бути унікальним у кожному потоці. Це звичайно тягне за собою збереження якоїсь контекстної інформації, яка пов'язана з поточним потоком виконання.
Ми використовуємо його в середовищі Java EE для передачі ідентичності користувача класам, які не відомі Java EE (не мають доступу до HttpSession або EJB SessionContext). Таким чином код, який використовує ідентичність для операцій, заснованих на захисті, може отримати доступ до ідентичності з будь-якого місця, без необхідності явно передавати його в кожному виклику методу.
Цикл запитів / відповідей операцій у більшості дзвінків Java EE полегшує цей тип використання, оскільки він дає чітко визначені точки входу та виходу для встановлення та зняття ThreadLocal.
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
Локальні змінні нитки часто використовуються для запобігання спільному використанню в проектах, заснованих на змінних 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.
Клас ThreadLocal в Java дозволяє створювати змінні, які можна читати і записувати лише з одного потоку. Таким чином, навіть якщо два потоки виконують один і той же код, і в коді є посилання на змінну ThreadLocal, два потоки не можуть бачити змінні ThreadLocal один одного.
Існує 3 сценарії використання помічника класу, як SimpleDateFormat у багатопотоковому коді, найкращим з яких є використання ThreadLocal
Сценарії
1- Використання подібного об'єкта для обміну за допомогою блокування або механізму синхронізації, що робить додаток повільним
2- Використання як локального об'єкта всередині методу
У цьому випадку, якщо у нас є 4 потоки, кожен з яких викликає метод 1000 разів, тоді у нас створено
4000 об’єктів SimpleDateFormat і чекає, коли GC видалить їх
3- Використання теми ThreadLocal
якщо у нас є 4 потоки, і ми дали кожному потоку один екземпляр SimpleDateFormat,
тому у нас є 4 потоки , 4 об'єкти SimpleDateFormat.
Немає необхідності механізму блокування та створення та знищення об'єктів. (Складність часу та просторова складність)
[Для довідки] ThreadLocal не може вирішити проблеми оновлення спільного об’єкта. Рекомендується використовувати об'єкт staticThreadLocal, який розділяється всіма операціями в одному потоці. Метод [Обов’язковий] delete () повинен бути реалізований змінними ThreadLocal, особливо при використанні пулів потоків, в яких потоки часто використовуються повторно. В іншому випадку це може вплинути на подальшу логіку бізнесу та спричинити несподівані проблеми, такі як витік пам'яті.
Кешуючи, колись вам доведеться обчислити однакове значення багато часу, тому зберігаючи останній набір входів для методу, і результат ви можете пришвидшити код. Використовуючи локальне зберігання теми, вам не доведеться думати про блокування.
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();
}
}
Threadlocal забезпечує дуже простий спосіб досягти повторного використання об'єктів з нульовою вартістю.
У мене була ситуація, коли кілька потоків створювали зображення кеша, що змінюється, у кожному повідомленні про оновлення.
Я використовував Threadlocal у кожному потоці, а потім кожну нитку потрібно було б просто скинути старе зображення, а потім оновити його знову з кеша кожного повідомлення про оновлення.
Звичайні об'єкти для багаторазового використання з пулів об'єктів мають пов'язані з ними витрати на безпеку потоків, тоді як такий підхід не має.
Спробуйте цей невеликий приклад, щоб відчути змінну 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'.