Синхронизация потоков в переменной экземпляра Integer
public class Main{
public static void main(String[] args) throws Exception {
// Creating objects for class Check(2 different objects)
Check c = new Check("s1");
Check c1 = new Check("s2");
c.start();c1.start();
}
}
class Check extends Thread{
Check(String name){super(name);}
private Integer ab = 2;
public void run(){
synchronized (ab) {
System.out.println(Thread.currentThread().getName());
for(int i=0;i<10;i++)System.out.print(i+" ");
}
}
}
Здесь я синхронизирован по переменной ab. И я создал два разных экземпляра класса Check также, но я всегда получаю вывод для s1, за которым следует s2 или наоборот, но не смешанный, почему это так? когда я уже создал два отдельных объекта (в основном), так что два разных потока, две разные переменные ab, и как он становится общим ресурсом для двух разных объектов?
Ответы
Ответ 1
TL; DR - из-за пула Integer
. Сделайте ab Object
(т.е. Object ab = new Object()
), чтобы гарантировать, что каждый экземпляр блокировки Check
не мешает другим.
Сначала я был озадачен. Интересно, если вы измените
private Integer ab = 2;
to
private Object ab = new Object();
синхронизация уходит (вы получаете разные выходы при каждом прогоне). Назад с ab как Integer
, я запустил ваш код в режиме отладки (с точкой останова в строке имени потока печати) и нашел следующее. Здесь первый поток:
![Первичные переменные потока]()
А вот второй поток.
![Вторые переменные потока]()
Обратите внимание, что это фактически тот же объект, [email protected]
. Даже если вы считали, что получаете два разных объекта, оба поля ab
в двух экземплярах проверки ссылаются на один и тот же объект в памяти! Поэтому да, есть правильная синхронизация. Таким образом, возникает вопрос: как сказать Integer ab = 2
дважды получает в памяти тот же объект Integer
.
Говоря Integer ab = 2
, вы используете autoboxing, с помощью которого примитивное значение (типа int
) автоматически преобразуется в соответствующий тип объекта Integer
. Это эквивалентно вызову метода метода автобоксинга:
private Integer ab = Integer.valueOf(2);
Если мы рассмотрим Integer.valueOf
, заметим, что он имеет пул для значений в определенном диапазоне:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Для большинства обычных настроек это будет включать значение 2
. Таким образом, вы получаете то же значение Integer
в памяти, когда вы вызываете этот метод.
Ответ 2
Это происходит из-за концепции Ingeter Pool в java.
Целые числа от -128 до 127 (включительно) используются так же, как и для пула строк.
Итак, когда вы используете private Integer ab = 2;
, ab используется для обоих объектов Check
.
Вы можете использовать значение > 128 или любой другой тип объекта, чтобы ваш код не синхронизировался.
Вы можете посмотреть ответы здесь: Почему поведение пула констант Integer изменяется на 127? для понимания концепции Integer Pool.
Ответ 3
Если вы посмотрите на байт-код, вы, вероятно, увидите этот код:
LINENUMBER 15 L1
ALOAD 0
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTFIELD com/example/Check.ab : Ljava/lang/Integer;
Java пытается привязать примитивное значение 2
к объекту, вызвав #valueOf
класса Integer
. И, как известно, Integer
valueOf
реализует шаблон Flyweight. Таким образом, один и тот же объект делится между экземплярами.