Подтвердить что ты не робот

Синхронизация потоков в переменной экземпляра 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, и как он становится общим ресурсом для двух разных объектов?

4b9b3361

Ответ 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. Таким образом, один и тот же объект делится между экземплярами.