Я узнал, что каждый код байта класса загружен в память однажды для каждого загрузчика класса, таким образом когда поток выполняет код байта некоторого метода, и другой поток приходит?
1 поток-> 1 экземпляр - класса Foo == без проблем.
X потоков-> 1 экземпляр - класса, Foo == должен быть обработан, это ясно.
X потоков-> X соответствующих экземпляров - класса Foo ==????
я должен удостовериться, что ничто не испорчено в методе? если метод использует уровень экземпляра переменные, я могу быть уверен, что он будет использовать правильные?
Обновление:
Я вижу, что мой вопрос не был ясен некоторым, вот пример с числами
У меня есть объект типа класса 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 экземпляров этого объекта, которые получают доступ к этому коду байта, поэтому если я хочу препятствовать тому, чтобы они наложились на том же коде байта, что я должен сделать?
Спасибо, Adam.
Я узнал, что каждый байт-код класса загружается в память один раз для каждого загрузчика классов, таким образом, когда поток выполняет байтовый код некоторого метода, и появляется другой поток?
Загрузка класса и байтовый код здесь не важны. Байт-код - это набор инструкций, подобных сборке, которые JVM интерпретирует и компилирует в собственный машинный код. Более одного потока могут безопасно следовать наборам инструкций, закодированным в байт-код.
1 поток -> 1 экземпляр - класса Test, нет проблем
В основном правильно. Если есть только один поток, то нет никакой немедленной необходимости делать что-либо потокобезопасным. Однако игнорирование безопасности потоков ограничит рост и возможность повторного использования.
X потоков -> 1 экземпляр - класса Test, необходимо обработать, это ясно.
Что ж, да, по причинам видимости потоков и для обеспечения того, чтобы критические области выполнялись атомарно, использование методов синхронизации или блокировки довольно важно. Конечно, все зависит от того, верно ?! Если у вашего класса нет состояния (переменных экземпляра или класса), вам действительно не нужно делать его потокобезопасным (подумайте о служебном классе, таком как Java Executors
, Arrays
, Коллекции
классов).
X потоков -> X соответствующих экземпляров - класса Test, ????
Если каждый поток имеет свой собственный экземпляр класса Test, и ни один экземпляр не используется более чем одним потоком, то это то же самое в качестве вашего первого примера. Если на экземпляр Test ссылаются два или более потоков, то это то же самое, что и ваш второй пример.
Если метод использует переменные уровня класса, могу ли я быть уверен, что он будет использовать правильные?
Переменные класса ARE статические
переменные в Java. Уже опубликовано два ответа, в которых подчеркивается важность использования synchronized
для предотвращения одновременного изменения переменной класса более чем одним потоком. Не упоминается важность использования synchronized
или volatile
, чтобы убедиться, что вы не видите устаревшую версию переменной класса.
Вам необходимо выполнить синхронизацию на общем ресурсе. Если этот ресурс является полем уровня класса, вам следует выполнить синхронизацию в самом классе:
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 уже является потокобезопасным, обратите внимание, что это всего лишь пример, и основная концепция может быть применена к чему угодно. )
Добавляя к ответу Дейва ответ, если у вас есть несколько статических методов, которые работают с разными статическими членами, лучше синхронизироваться на отдельных статических объектах.
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
, не застревая на синхронизации.
EDIT
Вот тот же пример с 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();
}
}
Все потоки должны обращаться к одному и тому же загрузчику классов. 10 потоков, использующих FOo.class, все 10 будут иметь один и тот же объект. Единственный способ получить один и тот же класс в разных загрузчиках классов - это если
a) вы написали свой собственный код, который делает магию загрузчика классов
b) Вы сделали что-то странное, например, включили свой код и в war, и в общую папку lib tomcat... и сделали правильную последовательность событий, чтобы две копии были загружены из разных мест.
Во всех нормальных случаях... вы создаете класс, существует ровно 1 копия объекта класса, и вся синхронизация по (этому) будет происходить по всему вашему приложению.