Любая причина НЕ пощелкать "синхронизированное" ключевое слово во всем мире?

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

Я не вижу, чтобы кто-нибудь еще писал код с < synchronized. Итак, есть ли какая-нибудь причина, по которой я не должен иметь < <20 > "всюду?

Что делать, если я не слишком забочусь о производительности (т.е. метод не вызывается более одного раза в несколько секунд)?

Ответ 1

Конечно - производительность. У мониторов есть стоимость.

Ответ не является ни удалением, ни добавлением синхронизации случайным образом. Лучше читать что-то вроде Brian Goetz "Java Concurrency In Practice" или Doug Lea "Книги Java Threads", чтобы понять, как это сделать правильно. Разумеется, изучите новые параллельные пакеты.

Многопоточность намного больше, чем синхронизированное ключевое слово.

Ответ 2

Если вы без разбора синхронизируете, вы также рискуете создать тупик .

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

public class Foo {

    synchronized void doSomething() {
        //code to doSomething
    }

    synchronized void doSomethingWithBar(Bar b) {
        b.doSomething();
    }

}

public class Bar {

    synchronized void doSomething() {
        //code to doSomething
    }

    synchronized void doSomethingWithFoo(Foo f) {
        f.doSomething();
    }
}

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

Чтобы заставить тупик, вы можете вставить спящий режим в оба метода doSomethingWith* (используя Foo в качестве примера):

synchronized void doSomethingWithBar(Bar b) {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }

    b.doSomething();
}

В вашем основном методе вы запускаете два потока для завершения примера:

public static void main(String[] args) {
    final Foo f = new Foo();
    final Bar b = new Bar();
    new Thread(new Runnable() {
        public void run() {
            f.doSomethingWithBar(b);
        }
    }).start();

    new Thread(new Runnable() {
        public void run() {
            b.doSomethingWithFoo(f);
        }
    }).start();
}

Ответ 3

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

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

Ответ 4

Точка производительности уже указана.

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

Ответ 5

Намного лучше, чем положить synchronized всюду, - это тщательно рассуждать об инвариантах, которые нужны вашим классам, а затем синхронизировать их достаточно, чтобы обеспечить эти инварианты. Если вы пересинхронизируете, вы создаете два риска:

  • тупиковый
  • проблемы с жизнеобеспечением.

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

Если вы хотите повредить ключевое слово везде для обеспечения безопасности, я рекомендую использовать final вместо synchronized. :) Все, что вы можете сделать final, способствует лучшей безопасности потоков и упрощению рассуждений о том, какие инварианты необходимо поддерживать блокировками. Чтобы привести пример, скажем, у вас есть этот тривиальный класс:

public class SimpleClass {
    private volatile int min, max;
    private volatile Date startTime;
    // Assume there are many other class members

    public SimpleClass(final int min, final int max) {
        this.min = min;
        this.max = max;
    }
    public void setMin(final int min) {
        // set min and verify that min <= max
    }
    public void setMax(final int max) {
        // set max and verify that min <= max
    }
    public Date getStartTime() {
        return startTime;
    }
}

С указанным выше классом при установке min или max вам необходимо синхронизировать. Вышеприведенный код нарушен. Какой у инварианта этот класс? Вы должны всегда гарантировать, что min <= max, даже когда несколько потоков вызывают setMin или setMax.

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

Ответ 6

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

Каждый объект sync'ed должен выполнить проверку некоторого вида, чтобы убедиться, что он еще не используется, а затем установите флаги при использовании и reset после завершения.

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

Ответ 7

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

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

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

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

Ответ 8

Скажем, у вас есть этот пример


synchronized (lock){
    doSomething();
}

Если вы заблокировали несколько потоков, вы не можете их прервать. Лучше использовать объекты Lock вместо "синхронизированного", потому что у вас есть лучший контроль над прерываниями, которые могут произойти. Конечно, как и все предлагаемые, есть также некоторые проблемы с производительностью с "синхронизированными", и если вы злоупотребляете ими, у вас могут быть взаимоблокировки, livelocks и т.д.

Ответ 9

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

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