Многопоточность на разных экземплярах одного и того же объекта в 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
И я думаю, что переменная экземпляра как локальная переменная уникальна для каждого потока. Таким образом, по умолчанию это потокобезопасно. Но да, по-прежнему необходимо следить за синхронизацией.