Это связано с добавлением дополнений, чтобы удовлетворить ограничениям выравнивания. Уравнение структуры данных влияет как на производительность, так и на правильность программ:
SIGBUS
). Вот пример использования типичных настроек для процессора x86 (все используемые 32 и 64-битные режимы):
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
Можно минимизировать размер структур путем сортировки (например, структура Z
в приведенном выше примере).
ВАЖНОЕ ЗАМЕЧАНИЕ: В стандартах C и C ++ указано, что выравнивание структуры определяется реализацией , Поэтому каждый компилятор может выбрать выравнивание данных по-разному, что приводит к разным и несовместимым макетам данных. По этой причине при работе с библиотеками, которые будут использоваться разными компиляторами, важно понять, как компиляторы выравнивают данные. Некоторые компиляторы имеют параметры командной строки и / или специальные #pragma
операторы для изменения настроек выравнивания структуры.
Если Вы используете, 'синхронизировался' на методе set здесь также, этот код ориентирован на многопотоковое исполнение. Однако это не может быть достаточно детализировано; если у Вас есть 20 методов считывания и методы set, и они все синхронизируются, можно создавать узкое место синхронизации.
В этом определенном экземпляре, с единственной международной переменной, затем устраняя 'синхронизируемый' и отмечая международное 'энергозависимое' поле также гарантирует видимость (каждый поток будет видеть последнее значение 'val' при вызове метода считывания), но это не может синхронизироваться достаточно для потребностей. Например, ожидание
int old = someThing.getVal();
if (old == 1) {
someThing.setVal(2);
}
установить val на 2, если и только если это уже 1, является неправильным. Для этого Вам нужна внешняя блокировка или некоторый атомарный compare-set.
Я настоятельно рекомендую, что Вы читаете Параллелизм Java На практике Brian Goetz и др., он имеет лучшее покрытие конструкций параллелизма Java.
От моего понимания необходимо использовать, синхронизировался и на методе считывания и на методах установщика, и это достаточно.
Править: Вот ссылка еще на некоторую информацию о синхронизации и что нет.
В дополнение к комментарию Кауэна Вы могли сделать следующее для сравнивания и хранилища:
synchronized(someThing) {
int old = someThing.getVal();
if (old == 1) {
someThing.setVal(2);
}
}
Это работает, потому что блокировка, определенная через синхронизированный метод, является неявно тем же как блокировкой объекта (см. спецификацию языка Java).
Для простых объектов это может быть достаточным. В большинстве случаев необходимо избежать синхронизируемого ключевого слова, потому что можно столкнуться с мертвой блокировкой синхронизации.
Пример:
public class SomeClass {
private Object mutex = new Object();
private int val = -1; // TODO: Adjust initialization to a reasonable start
// value
public int getVal() {
synchronized ( mutex ) {
return val;
}
}
private void setVal( int val ) {
synchronized ( mutex ) {
this.val = val;
}
}
}
Гарантирует что только одно чтение потока или записи к локальному члену экземпляра.
Прочитайте книгу "Параллельное Программирование в Java (TM): Принципы разработки и Шаблоны (Java (Addison-Wesley))", возможно, http://java.sun.com/docs/books/tutorial/essential/concurrency/index.html также полезен...
Синхронизация существует для защиты от ошибок непротиворечивости интерференции и памяти потока. Путем синхронизации на getVal (), код гарантирует, что другие синхронизированные методы для SomeClass также не выполняются одновременно. С тех пор нет никаких других синхронизированных методов, это не обеспечивает много значения. Также обратите внимание, что чтения и записи на примитивах имеют атомарный доступ. Это означает с тщательным программированием, не нужно синхронизировать доступ к полю.
Едва ли уверенный, почему это было отброшено к-3. Я просто суммирую то, что учебное руководство по Синхронизации от Sun говорит (а также мой собственный опыт).
Используя простой атомарный переменный доступ более эффективно, чем доступ к этим переменным через синхронизируемый код, но требует, чтобы больше ухода программистом избежало ошибок непротиворечивости памяти. Стоит ли дополнительное усилие, зависит от размера и сложности приложения.
Если ваш класс содержит только одну переменную, то другой способ обеспечения потоковой безопасности - использовать существующий объект AtomicInteger.
public class ThreadSafeSomeClass {
private final AtomicInteger value = new AtomicInteger(0);
public void setValue(int x){
value.set(x);
}
public int getValue(){
return value.get();
}
}
Однако, если вы добавляете дополнительные переменные, которые становятся зависимыми (состояние одной переменной зависит от состояние другого), то AtomicInteger работать не будет.
Повторяя предложение прочитать «Java Concurrency in Practice».