Если у вас есть только один поток писем, вам нужен специальный concurrency?

Предполагая, что:

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

В этом случае вам нужна изменчивая или AtomicReference или что-то в этом роде?

В этой статье говорится:

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

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

Итак, вот тест, который я провел с любопытными результатами:

import org.junit.Test;

public class ThreadTest {
    int onlyWrittenByMain = 0;
    int onlyWrittenByThread = 0;

    @Test
    public void testThread() throws InterruptedException {
        Thread newThread = new Thread(new Runnable() {
            @Override
            public void run() {
                do {
                    onlyWrittenByThread++;
                } while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10);
                System.out.println("thread done");
            }
        });
        newThread.start();

        do {
            onlyWrittenByMain++;
            // Thread.yield();
            // System.out.println("test");
            // new Random().nextInt();
        } while (onlyWrittenByThread < 10);
        System.out.println("main done");
    }
}

Иногда выполнение этого будет выводить "thread done", а затем зависать вечно. Иногда это заканчивается. Таким образом, поток видит изменения, которые делает основной поток, но, по-видимому, главное не всегда видит изменения, которые делает поток.

Если я вставил систему или Thread.yield или случайный вызов, либо сделайте onlyWrittenByThread volatile, он будет завершен каждый раз (пробовал примерно 10 + раз).

Означает ли это, что сообщение в блоге, приведенное выше, неверно? Что вы должны иметь барьер памяти даже в сценарии с одним сценарием?

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

Ответ 1

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

Ответ 2

Основная проблема в вашем коде - это не столько то, что процессор будет делать, но то, что JVM будет делать с ним: у вас есть высокий риск переменного подъема. Это означает, что JMM (модель памяти Java) позволяет JVM переписать:

public void run() {
    do {
        onlyWrittenByThread++;
    } while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10);
    System.out.println("thread done");
}

как этот другой фрагмент кода (обратите внимание на локальные переменные):

public void run() {
    int localA = onlyWrittenByMain;
    int localB = onlyWrittenByThread;
    do {
        localB ++;
    } while (localA < 10 || localB < 10);
    System.out.println("thread done");
}

Бывает, что это довольно распространенная оптимизация, созданная hotpost. В вашем случае, как только эта оптимизация будет сделана (возможно, не сразу, когда вы вызовете этот метод, но через несколько миллисекунд), то, что вы делаете в других потоках, будет никогда быть видимым из этого потока.

Ответ 3

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

Ответ 4

Я думаю, вы неправильно поняли слова, которые вы указали из блога Мартина. На оборудовании x86/64 вы можете использовать Atomic *.lazySet, чтобы получить гораздо большую производительность, чем "volatile", потому что он обеспечивает барьер хранилища, который намного дешевле, чем барьер для хранения. Насколько я понимаю, вы должны использовать AtomicLong вместо этого и использовать lazySet, чтобы гарантировать, что коды не переупорядочиваются.

Пожалуйста, обратитесь к этой статье за ​​дополнительной информацией: http://psy-lob-saw.blogspot.com/2012/12/atomiclazyset-is-performance-win-for.html