Нужна ли синхронизация во время чтения, если не может возникнуть конфликт - PullRequest
9 голосов
/ 02 декабря 2010

Рассмотрим код снайпера ниже:

package sync;

public class LockQuestion {
    private String mutable;

    public synchronized void setMutable(String mutable) {
        this.mutable = mutable;
    }

    public String getMutable() {
        return mutable;
    }   
}

Во время потока Time1 Thread1 обновит переменную 'mutable'. Синхронизация необходима в установщике для очистки памяти из локального кэша в основную память. Во время Time2 (Time2> Time1, поток не конфликтует) поток Thread2 будет считывать значение изменяемой переменной.

Вопрос - нужно ли ставить синхронизированный перед геттером? Похоже, это не вызовет никаких проблем - память должна быть обновленной, а локальная кэш-память Thread2 должна быть аннулирована и обновлена ​​Thread1, но я не уверен.

Ответы [ 6 ]

4 голосов
/ 02 декабря 2010

Хорошо, если вы сделаете изменчивую изменчивую, подробности в " дешевой блокировке чтения-записи "

4 голосов
/ 02 декабря 2010

Вместо того, чтобы удивляться, почему бы просто не использовать атомные ссылки в java.util.concurrent ?

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

1 голос
/ 02 декабря 2010

Я думаю, что вы должны начать с чего-то, что является правильным и оптимизировать позже, когда вы знаете, что у вас есть проблема. Я бы просто использовал AtomicReference, если несколько нано-секунд не слишком длинные. ;)

public static void main(String... args) {
    AtomicReference<String> ars = new AtomicReference<String>();
    ars.set("hello");
    long start = System.nanoTime();
    int runs = 1000* 1000 * 1000;
    int length = test(ars, runs);
    long time = System.nanoTime() - start;
    System.out.printf("get() costs " + 1000*time / runs + " ps.");
}

private static int test(AtomicReference<String> ars, int runs) {
    int len = 0;
    for (int i = 0; i < runs; i++)
        len = ars.get().length();
    return len;
}

Печать

get() costs 1219 ps.

ps - это пикосекунда с миллионной долей микросекунды.

1 голос
/ 02 декабря 2010

Если вы так сильно беспокоитесь о производительности в потоке чтения, то вы просто читаете значение один раз, используя правильную синхронизацию или переменные или атомарные ссылки. Затем вы присваиваете значение простой старой переменной.

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

1 голос
/ 02 декабря 2010

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

Если есть вероятность, что get и set могут быть вызваны одновременно, то вам определенно нужно синхронизировать два.

0 голосов
/ 02 декабря 2010

Это, вероятно, никогда не приведет к неправильному поведению, но если вы также не гарантируете порядок запуска потоков, вы не можете гарантировать, что компилятор не переупорядочил чтение в Thread2 перед записью в Thread1.В частности, вся среда выполнения Java должна только гарантировать, что потоки выполняются так, как если бы они выполнялись в последовательном .Таким образом, до тех пор, пока поток имеет один и тот же вывод, работающий последовательно с оптимизацией, весь языковой стек (компилятор, оборудование, среда выполнения языка) может делать практически все, что ему нужно.В том числе разрешив Thread2 кэшировать результат LockQuestion.getMutable().

На практике я был бы очень удивлен, если бы это когда-нибудь произошло.Если вы хотите гарантировать, что этого не произойдет, объявите LockQuestion.mutable как final и инициализируйте в конструкторе. Или используйте следующую идиому :

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}
...