java - статический метод неявно потокобезопасен?

К одному из моих статических методов java обращаются несколько потоков. Нужно ли явно синхронизировать метод с помощью ключевого слова synchronized?

Некоторое время назад я читал в книге, в которой говорится, что

статический метод неявно является потокобезопасным, поскольку метод не зависит от объекта

.

Хорошо известный пример - реализация Singleton. В котором getInstance() является статическим, и нужно ли нам также помечать его как синхронизированный?

 public synchronized static Logger getInstance() {
         if( instance == null ){
             instance = new Logger();
         }

         return instance;
     }

Спасибо


person JavaUser    schedule 09.08.2017    source источник
comment
нет! статический не является синонимом синхронизированного!   -  person ΦXocę 웃 Пepeúpa ツ    schedule 09.08.2017
comment
Если эти статические данные изменяемы, это означает, что каждый объект видит изменения. Это то, что вы хотите? Возможно нет.   -  person duffymo    schedule 09.08.2017
comment
Но метод будет иметь только одну копию, так как это переменная уровня класса. Правильно?   -  person JavaUser    schedule 09.08.2017
comment
Методы вообще не имеют копий. Я сильно подозреваю, что вы неправильно запомнили книгу — или вам следует процитировать ее дословно. Обычно статические методы должны быть сделаны потокобезопасными, но это не одно и то же.   -  person Jon Skeet    schedule 09.08.2017
comment
Статические методы не являются состоянием потока только потому, что они статические. Если в вашем классе есть статическое поле, скажем, x=1;, и у вас есть статический метод static void increment(){x++}, вы все равно можете столкнуться с проблемой состояния гонки. Статический просто означает, что метод вызывается в классе, а не в экземпляре. Если вы синхронизируете его, это просто означает, что вы синхронизировали его с литералом YourClass.class, а не с экземпляром this.   -  person Pshemo    schedule 09.08.2017
comment
@all, что, если эти данные - ThreadContext?   -  person Gaurav    schedule 07.12.2018


Ответы (5)


Модификатор static и потокобезопасность — это две разные вещи.

Вы находитесь в де-факто случае безопасности рекламы только тогда, когда у вас нет никаких условий гонки.
Отсутствие состояния гонки для метода означает, что:

  • либо доступ к методу осуществляется одним потоком

  • или параллельными потоками, но только для чтения.

Эти две вещи не имеют никакого отношения к тому, является ли метод статическим или нет.

Например, в этом коде:

public static Logger getInstance() {
     if( instance == null ){
         instance = new Logger();
     }

     return instance;
}

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


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

public class SingletonBillPughWithEagerCreation {

    // executed as soon as the SingletonBillPughWithEagerCreation class is loaded by the classLoader
    private static SingletonBillPughWithEagerCreation instance = new SingletonBillPughWithEagerCreation();

    private SingletonBillPughWithEagerCreation() {
    }

    public static SingletonBillPughWithEagerCreation getInstance() {
       return instance;
    }
}
person davidxxx    schedule 09.08.2017

Нет это не так.

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

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

class MyObject{
 static MyType mySharedObject; //if you access this in static methos you are not safe until you sync the access

public static void myMethod(){
   int localVar; //that is safely accessed
   mySharedObject.setSomething(pippo); //that is not safe in multi thread environment.

}

}

person Roberto Benazzato    schedule 09.08.2017
comment
«статическое поле объекта» — противоречие. Вы имеете в виду либо «статическое поле класса», либо «статическое поле, относящееся к объекту», однако статические поля примитивных типов или массивов также нуждаются в потокобезопасных конструкциях. Обратите внимание, что метод static может также манипулировать объектами, переданными в качестве параметров, которые могут совместно использоваться различными потоками. - person Holger; 21.09.2017

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

В этом примере показана функция, которая при вызове несколькими потоками может вызвать проблемы:

void doThreadNonsense(int input) {
  this.myValue = input;
  if (this.myValue > 9000) System.out.println("It's over 9000!");
}

Потому что здесь значение сначала записывается, а затем считывается несколькими потоками, и может случиться так, что поток A записывает 5, затем поток B записывает 9000, а затем поток A выводит предложение, потому что значение теперь > 9000, несмотря на ввод 5.

Однако с небольшим изменением эта функция становится потокобезопасной:

void doThreadNonsense(int input) {
  this.myValue = input;
  if (input > 9000) System.out.println("It's over 9000!");
}

Теперь значение записывается только несколькими потоками, но поскольку запись int происходит атомарно на любой 32-разрядной (или выше) машине, myValue записывается только одним потоком или другим, но никогда обоими одновременно. Этот пример показывает, как тщательно вам нужно проверить, что делает ваш код, чтобы выяснить, является ли он потокобезопасным.

Если вы используете статический метод, он может, как указано выше, также не быть потокобезопасным:

static int myValue;
static void doThreadNonsense(int input) {
  myValue = input;
  if (myValue > 9000) System.out.println("It's over 9000!");
}

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

static void doThreadNonsense(int input) {
  if (input> 9000) System.out.println("It's over 9000!");
}

Так что в вашей книге должно быть сказано: «Статические методы, которые следуют парадигме чистого кода, как правило, потокобезопасны».

Однако это совсем не полезно и полностью упускает из виду, почему что-то является потокобезопасным, а другое нет. Возьмем следующий пример, который на удивление (для некоторых) является потокобезопасным, несмотря на то, что он нарушает многие правила «чистой потоковой передачи»:

static final int[][] matrix = new int[N][M];

static void fillMatrixColumn(final int n) {
  for (int m = 0; m < M; ++m) {
    matrix[n][m] = calculateValue(n, m);
  }
}

public static void main(String[] args)() {
  IntStream.range(0, N)
    .parallel()
    .forEach(this::fillMatrixColumn);
  printMatrix(matrix);
}

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

person TwoThe    schedule 10.08.2017

Есть несколько ответов, объясняющих, почему это не потокобезопасно, но чтобы ответить на вашу цитату

статический метод неявно является потокобезопасным, поскольку метод не зависит от объекта

Инструкция JVM invokestatic для документов статических методов, которые

Если метод синхронизирован, монитор, связанный с разрешенным объектом Class, вводится или повторно вводится, как если бы выполнялась инструкция monitorenter (§monitorenter) в текущем потоке.

Это означает, что статические методы по своей сути не являются потокобезопасными в параллельной среде, поскольку существует разница между статическими методами synchronized и non-synchronized.

person Murat Karagöz    schedule 09.08.2017

Рассмотрим следующий пример. getInstance вызывается два раза потоками A и B.

line1:public **synchronized** static Logger getInstance() {
line2:    if( instance == null ){
line3:        instance = new Logger();
line4:    }
line5:
line6:    return instance;
line7:}

Возможный порядок выполнения может быть A2, B2, A3, B3, что означает, что строка 2 выполняется потоком A, затем потоком B, а затем строка 3 выполняется потоком A и B. Это приведет к instance == B.getInstance(), что означает Logger, который был создан, когда поток B вызвал getInstance. Тем не менее, поток A использует другой регистратор!

person user3673613    schedule 09.08.2017
comment
Этот ответ нуждается в большем контексте. Предпочтительно более краткое объяснение с большей точностью ответа на вопрос ОП. - person Jason V; 09.08.2017