Многопоточность на разных экземплярах одного и того же объекта в Java

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

1 поток → 1 экземпляр - класса Foo == без проблем.

X потоки → 1 экземпляр - класса Foo == необходимо обработать, это понятно.

X потоки → X соответствующих экземпляров - класса Foo == <? >

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

Update:

Я вижу, что мой вопрос не ясен для некоторых, вот пример с цифрами

У меня есть объект класса Foo, у которого нет синхронизации!

У меня есть 5 экземпляров этого Foo с 5 потоками, запущенными для каждого из них, и доступом к параметрам уровня экземпляра, например:

class FOO {
     private SomeObject someObject=new SomeObject();

     private void problematicMethod(Data data) {
         someObject.doSomethingWithTheData(data);
         data.doSomethingWithSomeObject(someObject); 
// any way you want it use the data or export the data
     }
}

Я спрашиваю, есть ли проблема здесь, поскольку есть только 1 байтовый код этого класса и 5 экземпляры этого объекта, которые обращаются к этому байтовому коду, поэтому если я хочу, чтобы они не перекрывались с одним и тем же байтовым кодом, что мне делать?

Спасибо, Адам.

Ответ 1

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

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

1 thread → 1 экземпляр - класса Test, без проблем

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

X threads → 1 экземпляр - класса Test, необходимо обработать это понятно.

Хорошо, да, для причин видимости потоков и для обеспечения того, чтобы критические регионы выполнялись атомарно, использование технологий синхронизации или блокировки довольно важно. Конечно, все в порядке? Если ваш класс не имеет состояния (переменные экземпляра или класса), то вам действительно не нужно делать его потокобезопасным (подумайте, что класс утилиты, такой как классы Java Executors, Arrays, Collections).

X threads → X соответствующих экземпляров - класса Test,

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

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

Переменные класса ARE static переменные в Java. Уже опубликовано два ответа, в которых подчеркивается важность использования synchronized для предотвращения одновременной модификации переменной класса более одного потока. Не упоминается важность использования synchronized или volatile, чтобы убедиться, что вы не видите устаревшую версию переменной класса.

Ответ 2

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

public class Foo {
  private static int someNumber = 0;
  // not thread safe
  public void inc_unsafe()  { 
    someNumber++;
  }

  // not thread safe either; we are sync'ing here on an INSTANCE of
  // the Foo class
  public synchronized void inc_also_unsafe()  { 
    someNumber++;
  }

  // here we are safe, because for static methods, synchronized will use the class
  // itself
  public static synchronized void inc_safe()  { 
    someNumber++;
  }

  // also safe, since we use the class itself
  public static synchronized void inc_also_safe()  { 
    synchronized (Foo.class) {
      someNumber++;
    }
  }
}

Обратите внимание, что {{java.util.concurrent}} предоставляет гораздо больше способов защищенных общих данных, чем ключевое слово Java {{synchronized}}. Посмотрите на него, поскольку это может быть то, что вы хотите.

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

Ответ 3

Добавление в dave answer, если у вас несколько статических методов, работающих на разных статических членах, лучше синхронизировать отдельные статические объекты.

public class Foo {
  private static int someNumber = 0;
  private static int otherNumber = 0;
  private static final Object lock1 = new Object();
  private static final Object lock2 = new Object();

  public static void incSomeNumber() {
    synchronized (lock1) {
      someNumber++;
    }
  }

  public static void incOtherNumber() {
    synchronized (lock2) {
      otherNumber++;
    }
  }
}

Таким образом, два разных потока могут одновременно вызывать incSomeNumber и incOtherNumber, не зацикливаясь на синхронизации.


ИЗМЕНИТЬ

Вот пример с AtomicInterger. Обратите внимание, что явная блокировка не требуется. Все операции над AtomicInterger являются атомарными и реализованы с использованием аппаратных операций. Таким образом, они обеспечивают лучшую производительность.

import java.util.concurrent.atomic.AtomicInteger;

public class Foo {
  private static AtomicInteger someNumber = new AtomicInteger(0);
  private static AtomicInteger otherNumber = new AtomicInteger(0);

  public static int incSomeNumber() {
    return someNumber.incrementAndGet();
  }

  public static int incOtherNumber() {
    return otherNumber.incrementAndGet();
  }
}

Ответ 4

Все потоки должны перейти к одному загрузчику классов. 10 потоков с использованием FOo.class, все 10 будут иметь одинаковый точный объект. Единственный способ получить один и тот же класс в разных загрузчиках классов - это если

a) Вы написали свой собственный код, который сделал волшебство загрузчика классов
б) Вы сделали что-то странное, как включить свой код как внутри войны, так и внутри общей папки libc tomcat... и сделали правильную последовательность событий, чтобы загрузить 2 копии из разных мест.

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

Ответ 5

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