2013-06-07 1 views
10

Предполагая:Если у вас только один поток писем, вам когда-нибудь нужен специальный параллелизм?

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

в этом случае вам нужно летучего или AtomicReference или что-нибудь подобное?

This article состояния:

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

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

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

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.yield или случайный вызов, либо сделайте onlyWrittenByThread изменчивым, он будет завершен каждый раз (проверено более 10 раз).

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

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

+2

Вам все равно придется беспокоиться о неполной записи, то есть чтение переменного, когда она была только частично написана. Если записи являются атомарными, вы в порядке. – theglauber

+3

Вам нужно как минимум 'volatile'. Нет никаких гарантий того, что запись в энергонезависимую переменную одним потоком будет невидимой для других потоков. 'volatile' гарантирует это! – fge

+1

@fge, можете ли вы предоставить какую-либо окончательную документацию для этого? Я пытаюсь прочитать главу 17 спецификации, но я не вижу, что поддерживает ваши претензии. Хотя, возможно, это отсутствие утверждения, опровергающего это пункт. – mentics

ответ

1

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

5

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

+0

Ситуация, о которой вы описываете, - это о постоянстве данных, не так ли? Если читательский поток читает данные один раз в секунду, не гарантировано ли в конечном итоге увидеть значение, которое написал поток писателя в какой-то момент? – mentics

+0

Это зависит от модели согласованности памяти базовой машины ... это вряд ли будет проблемой. Дело в том, что JLS не указывает, что это нужно - это причина для отношений, происходящих до JMM. – selig

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.В вашем случае, как только эта оптимизация будет сделана (вероятно, не сразу, когда вы вызываете этот метод, но через несколько миллисекунд), то, что вы делаете в других потоках, будет никогда быть видимым из этой темы.

+0

Правильно, это то, что я тоже догадывался. Ключевое слово volatile исключает эту оптимизацию. – mentics

+0

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

1

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

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

Смежные вопросы