Создание класса Thread-Safe
Дано:
public class TestSeven extends Thread {
private static int x;
public synchronized void doThings() {
int current = x;
current++;
x = current;
}
public void run() {
doThings();
}
}
Какое утверждение верно?
а. Ошибка компиляции.
В. Исключение создается во время выполнения.
С. Синхронизация метода run() сделает класс потокобезопасным.
Д. Данные в переменной "x" защищены от одновременных проблем доступа.
Е. Объявление метода doThings() как static сделает класс потокобезопасным.
F. Обертка операторов в doThings() в синхронизированном (новый объект()) {} блок сделает класс потокобезопасным.
недостаточно ли отмечать doThings() как синхронизированный, чтобы сделать этот класс потокобезопасным? я вижу, что правильный ответ - D, но модельный ответ этого вопроса - E, но я не понимаю, почему?
Ответы
Ответ 1
Е. Объявление метода doThings() как static сделает класс потокобезопасным.
Это своего рода сложный ответ. Метод уже синхронизирован, но в экземпляре, тогда как состояние находится в статическом поле, то есть в классе. Сделать его static synchronized
действительно правильным ответом, потому что тогда он синхронизируется с классом, а не на (бессмысленном) экземпляре.
Д. Данные в переменной "x" защищены от одновременных проблем доступа.
private static int x;
Это статическая переменная. Он разделяется всеми экземплярами класса, поэтому синхронизация в отдельных экземплярах не помогает, так же, как F не будет полезен, что синхронизируется с полным удаленным фиктивным объектом.
Ответ 2
В соответствии с спецификацией языка:
Синхронизированный метод получает монитор (§17.1) перед его выполнением.
Для класса (статического) метода монитор, связанный с классом объект для класса метода.
Для метода экземпляра монитор, связанный с этим (объект для которого был вызван метод).
Это означает, что в коде, который вы предоставили, ключевое слово synchronized
заставляет метод получать блокировку на this
перед тем, как выполнить тело метода. Однако, поскольку x
- static
, который не гарантирует, что обновление до x
будет атомарным. (Другой экземпляр класса может войти в синхронную область и выполнить обновление одновременно, так как они имеют разные значения this
и, следовательно, различные блокировки.)
Однако объявление doStuff
static приведет к тому, что все вызовы метода получат одну и ту же блокировку (тот, что находится на Class
), и таким образом обеспечит взаимное исключение в теле метода.
В спецификациях действительно указано, что:
class A {
static synchronized void doSomething() {
// ...
}
}
- буквально то же самое, что
class A {
static void doSomething() {
synchronized(A.class) {
// ...
}
}
}
Аналогично:
class B {
synchronized void doSomething() {
// ...
}
}
- буквально то же самое, что
class B {
void doSomething() {
synchronized (this) {
// ...
}
}
}
Ответ 3
В целях синхронизации метода doThings() вы удерживаете блокировку для определенного объекта TestSeven. Однако статические переменные класса не относятся к конкретному экземпляру самого объекта. Они принадлежат объекту Class TestSeven.class
. Таким образом, вы можете пойти на
synchronized (TestSeven.class){
int current = x;
current++;
x = current;
}
внутри метода doThings(), который получает блокировку класса внутри блокировки экземпляра, которая переусердствует. Таким образом, вы можете пометить метод как статический, чтобы вы в конечном итоге приобрели блокировку объекта класса.
Ответ 4
Так как x
является static
, другие потоки могут изменять его одновременно с тем, как работает метод doThings
. Создание doThings
static
остановит это.
Ответ 5
Я согласен с вами в том, что правильный ответ - D.
Я бы сказал, что E неверно, потому что, если я устанавливаю doThings() как статический и удаляю синхронизированное ключевое слово, я мог бы просто запустить 50 потоков TestSeven, и это может привести к неправильному значению x.
Примечание:
Я ошибся здесь, я пропустил тот факт, что синхронизированный метод без статики фактически использует экземпляр в качестве монитора блокировки вместо самого класса.