Невозможно многопоточность ConcurrentHashMap с использованием параллельного потока [дубликат]

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

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

      /* Create or update. */
      NotificationChannel channel = new NotificationChannel("my_channel_01",
          "Channel human readable title", 
          NotificationManager.IMPORTANCE_DEFAULT);
      mNotificationManager.createNotificationChannel(channel);
  }

Также вам нужно использовать каналы только в том случае, если ваша targetSdkVersion 26 или выше.

Если вы используете NotificationCompat.Builder, вам также необходимо обновить бета-версию библиотеки поддержки: https://developer.android.com/topic/libraries/ support-library / revisions.html # 26-0-0-beta2 (чтобы иметь возможность вызвать setChannelId в компоновщике сопоставления.)

Будьте осторожны, так как это обновление библиотеки повышает minSdkLevel до 14.

72
задан Solomonoff's Secret 15 January 2016 в 22:50
поделиться

3 ответа

Я нашел отчет об ошибке очень похожего случая ( JDK-8143380 ), который был закрыт как «Не проблема» Стюарта Маркса:

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

Тестовая программа должна быть изменена для перемещения логики параллельного потока вне статического инициализатора класса. Закрытие как не проблема.


Я смог найти еще один отчет об ошибке ( JDK-8136753 ), также закрытый как «Не проблема» Стюарта Маркса:

Это тупик, который возникает из-за того, что статический инициализатор Fruit enum плохо взаимодействует с инициализацией класса.

См. Спецификацию языка Java, раздел 12.4.2 для получения подробных сведений о классе инициализация.

http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2

Вкратце, что происходит:

  1. Основной поток ссылается на класс Fruit и запускает процесс инициализации. Это устанавливает флаг инициализации в прогресс и запускает статический инициализатор в основном потоке.
  2. Статический инициализатор запускает некоторый код в другом потоке и ждет его завершения. В этом примере используются параллельные потоки, но это не имеет ничего общего с потоками.
  3. Код в другом потоке ссылается на класс Fruit, который проверяет флаг инициализации в процессе выполнения. Это заставляет другой поток блокироваться до тех пор, пока флаг не будет очищен. (См. Шаг 2 в JLS 12.4.2.)
  4. Основной поток заблокирован, ожидая завершения другого потока, поэтому статический инициализатор никогда не завершается. Поскольку флаг инициализации в процессе не очищается до тех пор, пока не завершится статический инициализатор, потоки зашли в тупик.

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

Закрытие как не проблема.


Обратите внимание, что FindBugs имеет открытую проблему для добавления предупреждения для этой ситуации.

63
ответ дан Tunaki 1 September 2018 в 04:38
поделиться

Для тех, кто задается вопросом, где другие потоки, ссылающиеся на класс Deadlock, Java lambdas ведут себя так, как вы написали это:

public class Deadlock {
    public static int lambda1(int i) {
        return i;
    }
    static {
        IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return lambda1(operand);
            }
        }).count();
        System.out.println("done");
    }
    public static void main(final String[] args) {}
}

С обычными анонимными классами нет взаимоблокировки:

public class Deadlock {
    static {
        IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return operand;
            }
        }).count();
        System.out.println("done");
    }
    public static void main(final String[] args) {}
}
16
ответ дан chwarr 1 September 2018 в 04:38
поделиться

Существует прекрасное объяснение этой проблемы Andrei Pangin , датированное 07 апреля 2015 года. Здесь доступно , но оно написано на русском языке (я предлагаю просмотрите образцы кода в любом случае - они являются международными). Общая проблема - это блокировка во время инициализации класса.

Вот некоторые цитаты из статьи:


Согласно JLS , каждый класс имеет уникальную блокировку инициализации который фиксируется во время инициализации. Когда другой поток пытается получить доступ к этому классу во время инициализации, он будет заблокирован в блокировке до завершения инициализации. Когда классы инициализируются одновременно, можно получить тупик.

Я написал простую программу, которая вычисляет сумму целых чисел, что она должна печатать?

public class StreamSum {
    static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();

    public static void main(String[] args) {
        System.out.println(SUM);
    }
} 

Теперь удалите parallel() или заменить лямбда на вызов Integer::sum - что изменится?

Здесь мы снова увидим тупик [были некоторые примеры блокировок в инициализаторах класса, ранее описанных в этой статье]. Из-за операций потока parallel() выполняется в отдельном пуле потоков. Эти потоки пытаются выполнить тело лямбда, которое записывается в байт-код как метод private static внутри класса StreamSum. Но этот метод не может быть выполнен до завершения статического инициализатора класса, который ждет результатов завершения потока.

Что более разумно: этот код работает по-разному в разных средах. Он будет корректно работать на одном процессоре и, скорее всего, будет работать на многопроцессорной машине. Это различие связано с реализацией пула Fork-Join. Вы можете сами убедиться, что вы изменяете параметр -Djava.util.concurrent.ForkJoinPool.common.parallelism=N

9
ответ дан Community 1 September 2018 в 04:38
поделиться
Другие вопросы по тегам:

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