Подтвердить что ты не робот

Цель ThreadLocal?

Назначение ThreadLocal, как указано здесь, гласит, что переменная является локальной для любого потока, обращающегося к объекту, содержащему переменную ThreadLocal. Какая разница, если иметь переменную ThreadLocal в качестве члена класса, а затем сделать ее локальной для потока, а не иметь локальную переменную для самого потока?

4b9b3361

Ответ 1

Поток - это единица выполнения, поэтому несколько потоков могут выполнять один и тот же код одновременно. Если несколько объектов выполняются на объекте/экземпляре одновременно, они будут совместно использовать переменные экземпляра. Каждый поток будет иметь свои локальные переменные, но их сложно разделить между объектами без передачи параметров.

Это лучше всего объяснить на примере. Допустим, у вас есть сервлет, который получает зарегистрированного пользователя, а затем выполняет некоторый код.

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething()
  doSomethingElse()
  renderResponse(resp)
}

Теперь, что произойдет, если метод doSomething() нуждается в доступе к объекту пользователя? Вы не можете сделать пользовательский объект экземпляром или статической переменной, потому что тогда каждый поток будет использовать один и тот же пользовательский объект. Вы можете передать пользовательский объект в качестве параметра, но это быстро становится грязным и приводит к утечке пользовательских объектов в каждый вызов метода:

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething(user)
  doSomethingElse(user)
  renderResponse(resp,user)
}

Более элегантное решение - поместить объект пользователя в ThreadLocal.

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  StaticClass.getThreadLocal().set(user)
  try {
    doSomething()
    doSomethingElse()
    renderResponse(resp)
  }
  finally {
    StaticClass.getThreadLocal().remove()
  }
}

Теперь любой код, который требует пользовательского объекта в любое время, может получить его, извлекая его из локального потока, без необходимости прибегать к этим надоедливым дополнительным параметрам:

User user = StaticClass.getThreadLocal().get()

Если вы используете этот подход, будьте внимательны, чтобы снова удалить объекты в блоке finally. В противном случае пользовательский объект может зависать в средах, использующих пул потоков (например, сервер приложений Tomcat).

Изменение: код для статического класса

class StaticClass {
  static private ThreadLocal<User> threadLocal = new ThreadLocal<>();

  static ThreadLocal<User> getThreadLocal() {
    return threadLocal;
  }
}

Ответ 2

Вы должны понимать, что экземпляр класса, который расширяет Thread, - это не то же самое, что фактический поток Java (который можно представить как "указатель выполнения", который проходит через ваш код и выполняет его).

Экземпляры такого класса представляют собой поток Java и позволяют манипулировать им (например, прерывать его), но помимо этого они являются просто обычными объектами, и их члены могут быть доступны из всех потоков, которые могут получить ссылку на объект (который не жесткий).

Конечно, вы можете попытаться сохранить личность члена и убедиться, что он используется только с помощью run() или методов, вызванных из него (общедоступные методы могут быть вызваны и из других потоков), но это подвержено ошибкам и не совсем реально для более сложной системы, где вы не хотите хранить данные в подклассе Thread (на самом деле вы не должны подклассировать Thread, но вместо этого использовать Runnable).

ThreadLocal - это простой и гибкий способ иметь данные по потоку, к которым нельзя одновременно обращаться другими потоками, не требуя больших усилий или компрометаций проекта.

Ответ 3

Объект Thread может иметь внутренние члены данных, но они доступны для всех, кто имеет (или может получить) ссылку на объект Thread. ThreadLocal намеренно ассоциируется только с каждым Thread, который обращается к нему. Преимущество состоит в том, что нет проблем concurrency (в контексте ThreadLocal). Внутренний элемент данных Thread имеет все те же concurrency проблемы, с которыми связано какое-либо общее состояние.

Позвольте мне объяснить идею сопоставления результата с конкретным потоком. Суть ThreadLocal заключается в следующем:

public class MyLocal<T> {
  private final Map<Thread, T> values = new HashMap<Thread, T>();

  public T get() {
    return values.get(Thread.currentThread());
  }

  public void set(T t) {
    values.put(Thread.currentThread(), t);
  }
}

Теперь это больше, чем это, но, как вы можете видеть, возвращаемое значение определяется текущим потоком. Вот почему он локален для каждого потока.

Ответ 4

ThreadLocal очень полезен в web-приложениях. Типичный шаблон заключается в том, что где-то в начале обработки веб-запроса (обычно в фильтре сервлетов) состояние сохраняется в переменной ThreadLocal. Поскольку вся обработка запроса выполняется в 1 потоке, все компоненты, участвующие в запросе, имеют доступ к этой переменной.

Ответ 5

В wikipedia запись о этой проблемной области. В нашей среде он обычно используется для того, чтобы запрашивать локальные запросы. На стороне сервера запрос обрабатывается в основном одним потоком. Чтобы сохранить локальные данные, вы помещаете свои данные, например. данные сеанса, в локальной переменной потока. Эти данные невидимы для другого запроса (потоков), и поэтому вам не нужно синхронизировать его с другими запросами.

И не забывайте, что существуют конструкции JAVA API, которые являются не потокобезопасными, например. DateFormat. Статический экземпляр DateFormat просто не работает на стороне сервера.

На самом деле, легче обрабатывать многопоточное программирование, когда вы используете собственную личную копию данных, чем обработку с помощью блокировок и мониторов.

Ответ 6

Преимущество ThreadLocals заключается в том, что они могут использоваться методами, запущенными на простых ванильных потоках... или в любом подклассе Thread.

В отличие от этого, если ваши локаторы потоков должны быть реализованы как члены пользовательского подкласса Thread, есть много вещей, которые вы не можете сделать. Например, ваше приложение будет в затруднении, если ему нужно запустить методы на ранее существовавших виниловых экземплярах Thread; то есть экземпляры, которые были созданы с помощью некоторого кода библиотеки, который писатель приложения не записывал и не может изменять.