Синхронизация на Строке возражает в Java

Исключение нулевого указателя генерируется, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  1. Вызов метода экземпляра объекта null.
  2. Доступ или изменение поля объекта null.
  3. Принимая длину null, как если бы это был массив.
  4. Доступ или изменение слотов null, как если бы это был массив.
  5. Бросок null как будто это было значение Throwable.

Приложения должны бросать экземпляры этого класса, чтобы указать на другие незаконные использования объекта null.

Ссылка: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html

42
задан Vadzim 11 November 2017 в 18:29
поделиться

11 ответов

Не помещая мой мозг полностью в механизм, от быстрого сканирования того, что Вы говорите, что выглядит, как будто необходимо интернировать () Строки:

final String firstkey = "Data-" + email;
final String key = firstkey.intern();

Две Строки с тем же значением являются иначе не обязательно тем же объектом.

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

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

Ответ на обновление вопроса :

я думаю поэтому, что строковый литерал всегда приводит к тому же объекту. Dave Costa указывает в комментарии, что это еще лучше, чем это: литерал всегда приводит к каноническому представлению. Таким образом, все Строковые литералы с тем же значением где угодно в программе привели бы к тому же объекту.

Редактирование

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

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

Вот альтернатива - она все еще использует исключительную блокировку, но мы знаем, что собираемся нуждаться в одном из тех для кэша так или иначе, и Вы говорили приблизительно 50 потоков, не 5000, так, чтобы могло не быть фатальным. Я также предполагаю, что узкое место производительности здесь является медленным вводом-выводом блокирования в DoSlowThing (), который поэтому чрезвычайно извлечет выгоду из того, чтобы не быть сериализированным. Если это не узкое место, то:

  • , Если ЦП занят тогда, этот подход не может быть достаточным, и Вам нужен другой подход.
  • , Если ЦП не занят, и доступ к серверу, не узкое место, то этот подход является излишеством, и Вы могли бы также забыть и эту и блокировку на ключ, поместить большой синхронизируемый (StaticCache) вокруг целой операции и сделайте это простой способ.

, Очевидно, этот подход должен быть замачиванием, протестированным на масштабируемость перед использованием - я ничего не гарантирую.

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

IN_PROGRESS является фиктивным значением - не точно чистый, но простой код, и это сохраняет наличие двух хеш-таблиц. Это не обрабатывает InterruptedException, потому что я не знаю то, что Ваше приложение хочет сделать в этом случае. Кроме того, если DoSlowThing () последовательно перестал работать для данного, включают этот код как есть не точно изящно, так как каждый поток через повторит его. Так как я не знаю то, что критерии отказа, и склонны ли они быть временными или постоянными, я не обрабатываю это также, я просто удостоверяюсь, что потоки не блокируются навсегда. На практике можно хотеть поместить значение данных в кэш, который указывает 'не доступный', возможно, с причиной и тайм-аутом для того, когда повторить.

// do not attempt double-check locking here. I mean it.
synchronized(StaticObject) {
    data = StaticCache.get(key);
    while (data == IN_PROGRESS) {
        // another thread is getting the data
        StaticObject.wait();
        data = StaticCache.get(key);
    }
    if (data == null) {
        // we must get the data
        StaticCache.put(key, IN_PROGRESS, TIME_MAX_VALUE);
    }
}
if (data == null) {
    // we must get the data
    try {
        data = server.DoSlowThing(key);
    } finally {
        synchronized(StaticObject) {
            // WARNING: failure here is fatal, and must be allowed to terminate
            // the app or else waiters will be left forever. Choose a suitable
            // collection type in which replacing the value for a key is guaranteed.
            StaticCache.put(key, data, CURRENT_TIME);
            StaticObject.notifyAll();
        }
    }
}

Каждый раз, когда что-либо добавляется к кэшу, все потоки просыпаются и проверяют кэш (неважно, какой ключ они после), таким образом, возможно получить лучшую производительность с менее спорными алгоритмами. Однако большая часть той работы произойдет в течение Вашего обильного неактивного процессорного времени, блокируясь на вводе-выводе, таким образом, это не сможет быть проблема.

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

39
ответ дан 9 revs 26 November 2019 в 23:31
поделиться

Я также предложил бы избавиться от конкатенации строк полностью, если Вам не нужна она.

final String key = "Data-" + email;

там другие вещи/типы объектов в кэше, которые используют адрес электронной почты, что Вам нужны те дополнительные "Данные -" в начале ключа?

в противном случае я просто сделал бы это

final String key = email;

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

0
ответ дан John Gardner 26 November 2019 в 23:31
поделиться

Почему не только представляют статическую страницу HTML, которая подается пользователю и повторно создала каждый x минуты?

0
ответ дан MattW. 26 November 2019 в 23:31
поделиться

Ваша основная проблема не состоит просто в том, что могло бы быть несколько экземпляров Строки с тем же значением. Основная проблема состоит в том, что у Вас должен быть только один монитор, на котором можно синхронизироваться для доступа к объекту StaticCache. Иначе несколько потоков могли бы закончить тем одновременно, что изменили StaticCache (хотя под различными ключами), который, скорее всего, не поддерживает параллельную модификацию.

2
ответ дан Alexander 26 November 2019 в 23:31
поделиться

Используйте достойную платформу кэширования такой в качестве ehcache.

Реализация хорошего кэша не так легка, как некоторые люди верят.

Относительно комментария, что String.intern () является источником утечек памяти, который на самом деле не верен. Интернированные Строки , собрал "мусор", это просто могло бы занять больше времени, потому что на определенной JVM (SUN) они хранятся в пермском пространстве, которое только затронуто полным GC.

4
ответ дан kohlerm 26 November 2019 в 23:31
поделиться

Можно использовать 1,5 утилиты параллелизма для обеспечения кэша, разработанного, чтобы предоставить нескольким параллельный доступ и единственную точку дополнения (т.е. только один поток, когда-либо выполняющий дорогое объектное "создание"):

 private ConcurrentMap<String, Future<SomeData[]> cache;
 private SomeData[] getSomeDataByEmail(final WebServiceInterface service, final String email) throws Exception {

  final String key = "Data-" + email;
  Callable<SomeData[]> call = new Callable<SomeData[]>() {
      public SomeData[] call() {
          return service.getSomeDataForEmail(email);
      }
  }
  FutureTask<SomeData[]> ft; ;
  Future<SomeData[]> f = cache.putIfAbsent(key, ft= new FutureTask<SomeData[]>(call)); //atomic
  if (f == null) { //this means that the cache had no mapping for the key
      f = ft;
      ft.run();
  }
  return f.get(); //wait on the result being available if it is being calculated in another thread
}

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

5
ответ дан oxbow_lakes 26 November 2019 в 23:31
поделиться

Другие предложили интернировать строки, и это будет работать.

проблема состоит в том, что Java должен иметь в наличии интернированные строки. Мне сказали, что это делает это, даже если Вы не держите ссылку, потому что значение должно быть тем же в следующий раз, когда кто-то использует ту строку. Это означает интернировать все строки, может начать съедать память, которую с загрузкой Вы описываете, могла быть большая проблема.

я видел два решения этого:

Вы могли синхронизироваться на другом объекте

Вместо электронной почты, сделать объект, который содержит электронную почту (скажите Пользовательский объект), который содержит значение электронной почты как переменная. Если у Вас уже есть другой объект, который представляет человека (скажите, что Вы уже вытянули что-то от DB на основе их электронной почты), Вы могли использовать это. Путем реализации равняется методу и методу хэш-кода, можно удостовериться, что Java считает объекты тем же, когда Вы делаете статический cache.contains (), чтобы узнать, находятся ли данные уже в кэше (необходимо будет синхронизироваться в кэше).

На самом деле, Вы могли сохранить вторую Карту для объектов соединиться. Что-то вроде этого:

Map<String, Object> emailLocks = new HashMap<String, Object>();

Object lock = null;

synchronized (emailLocks) {
    lock = emailLocks.get(emailAddress);

    if (lock == null) {
        lock = new Object();
        emailLocks.put(emailAddress, lock);
    }
}

synchronized (lock) {
    // See if this email is in the cache
    // If so, serve that
    // If not, generate the data

    // Since each of this person's threads synchronizes on this, they won't run
    // over eachother. Since this lock is only for this person, it won't effect
    // other people. The other synchronized block (on emailLocks) is small enough
    // it shouldn't cause a performance problem.
}

Это предотвратит 15 выборок на том же адресе электронной почты в одном. Вам будет нужно что-то, чтобы препятствовать тому, чтобы слишком много записей закончились в карте emailLocks. Используя LRUMap с от Apache палата общин сделала бы это.

Этому будет нужна некоторая тонкая настройка, но она может решить Вашу проблему.

Использование различный ключ

, Если Вы готовы выносить возможные ошибки (я не знаю, насколько важный это) Вы могли использовать хэш-код Строки как ключ. ints не должны быть интернированы.

Сводка

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

5
ответ дан MBCook 26 November 2019 в 23:31
поделиться

Строки не хорошие кандидаты на синхронизацию. Если необходимо синхронизироваться на Идентификаторе строки, он может быть сделан при помощи строки для создания взаимного исключения (см." синхронизироваться на идентификаторе "). Стоит ли стоимость того алгоритма того, зависит от того, включает ли вызов Вашего сервиса любой значительный ввод-вывод.

Также:

  • я надеюсь StaticCache.get () и набор () , методы ориентированы на многопотоковое исполнение.
  • String.intern () прибывает в стоимость (тот, который варьируется между реализациями VM), и должен использоваться с осторожностью.
11
ответ дан McDowell 26 November 2019 в 23:31
поделиться

Синхронизация на Строке intern'd не могла бы быть хорошей идеей вообще - путем интернирования его, Строка превращается в глобальный объект, и если Вы синхронизируетесь на тех же интернированных строках в различных частях Вашего приложения, Вы могли бы получить действительно странные и в основном undebuggable проблемы синхронизации, такие как мертвые блокировки. Могло бы казаться маловероятным, но когда это происходит, Вы действительно завинчены. Как правило только когда-либо синхронизируйтесь на локальном объекте, где Вы абсолютно уверены, что никакой код за пределами Вашего модуля не мог бы заблокировать его.

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

, Например:

Object data = StaticCache.get(key, ...);
if (data == null) {
  Object lock = lockTable.get(key);
  if (lock == null) {
    // we're the only one looking for this
    lock = new Object();
    synchronized(lock) {
      lockTable.put(key, lock);
      // get stuff
      lockTable.remove(key);
    }
  } else {
    synchronized(lock) {
      // just to wait for the updater
    }
    data = StaticCache.get(key);
  }
} else {
  // use from cache
}

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

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

, С другой стороны, и намного легче, можно сделать целый метод поиска кэша ("getSomeDataByEmail"), синхронизировался. Это будет означать, что все потоки должны синхронизироваться, когда они получают доступ к кэшу, который мог бы быть проблемой производительности. Но как всегда, попробуйте это простое решение сначала и посмотрите, является ли это действительно проблема! Во многих случаях это не должно быть, поскольку Вы, вероятно, проводите намного больше времени, обрабатывая результат, чем синхронизация.

27
ответ дан Martin Probst 26 November 2019 в 23:31
поделиться

Вызов:

   final String key = "Data-" + email;

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

Это далее объясняет Ваше редактирование. Когда у Вас будет статическая строка, тогда она будет работать.

Используя интерн () решает проблему, потому что она возвращает строку из внутреннего пула, сохраненного Строковым классом, который гарантирует, что, если две строки равны, та в пуле будет использоваться. См.

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern ()

2
ответ дан Mario Ortegón 26 November 2019 в 23:31
поделиться

Последнее обновление 2019,

, Если Вы ищете новые способы реализовать синхронизацию в JAVA, этот ответ для Вас.

enter image description here

я нашел этот удивительный блог Anatoliy Korovin, это поможет Вам понять синхронизируемый глубоко.

, Как Синхронизировать Блоки Значением Объекта в Java.

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

0
ответ дан 26 November 2019 в 23:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: