Блок синхронизации работает не так, как ожидалось, при получении блокировки на требуемом объекте, но отлично работает при применении к «этому»

#java #multithreading #concurrency #synchronization

Вопрос:

 public class SyncBlockIssue {    public static void main(String[] args) {   Thread r1= new Multi();  Thread t1= new Thread(r1);   Thread t2= new Thread(r1);  t1.start();  t2.start();  }  }   class Multi extends Thread  {  Integer count =new Integer(0);   @Override  public void run() {  for(int i=0;ilt;100000;i  )  {   synchronized (count) {   //synchronized (this) { //working fine with this  count  ;  }   }  System.out.println(Thread.currentThread().getName()   " " count);   }   }  

Приведенный выше код печатает 2 миллиона из 2-го потока, независимо от того, сколько раз я запускаю, когда получаю блокировку на «этом», но где при печати произвольно учитывается, когда приобретается объект блокировки «подсчета». не мог бы кто-нибудь объяснить разницу.

Комментарии:

1. Примечание в общем стиле кода: никогда не передавайте поток конструктору другого потока. Ваш класс Multi не должен расширяться Thread , а просто реализовываться Runnable . Меньший момент: никогда не используйте new при упаковке примитивный тип. Просто Integer count = 0; сойдет. Начиная с Java 9, вы получите предупреждение при использовании new Integer(0)

Ответ №1:

Вот мое предположение, так как я не могу использовать комментарии для ввода такого количества слов.

Integer объекты неизменяемы:

 /**  * The value of the {@code Integer}.  *  * @serial  */  private final int value;   /**  * Constructs a newly allocated {@code Integer} object that  * represents the specified {@code int} value.  *  * @param value the value to be represented by the  * {@code Integer} object.  */  public Integer(int value) {  this.value = value;  }  

Итак, когда вы это сделаете count , Integer будет создан новый объект. Затем count ссылка направляется на новый объект.

Предположим, что следующий поток:

  1. Thread r1 приобретает count блокировку (обратите внимание, что count object блокируется), в этот момент thread r2 блокируется.
  2. r1 вызывает count , создается новый Integer объект. Затем count ссылка направляется на новый объект. И r1 отпустите замок.(Эта блокировка относится к предыдущему целочисленному объекту).
  3. r2 который был заблокирован и заблокирован.
  4. r1 также получает блокировку, потому count что уже указал на новый объект.
  5. Оба потока выполняются count : возникает проблема.

Следующий код доказывает, что оба потока удерживают «блокировку» одновременно

 for(int i=0; ilt;2; i  )  {  synchronized (count) {  System.out.println(Thread.currentThread().getName()   "get lock");  count  ;  if (i == 1) {  try {  Thread.sleep(100000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  System.out.println(Thread.currentThread().getName()   "release lock");  }  }  

Комментарии:

1. Обратите внимание , что когда у вас есть такая конструкция, как synchronized (count) … блок чтения count happens outside the synchronized, прежде чем его можно будет ввести. Таким образом, нет никакой гарантии, что поток увидит новые значения, написанные другим потоком внутри synchronized блока, поскольку эта гарантия существует только для правильной синхронизации кода с одним и тем же объектом. Таким образом , даже если второй поток еще не достиг этой точки, когда это делает первый count , он все равно может работать со старым объектом, в то время как первый продолжает работу с новым.

2. Существует также гонка данных, которая может привести к странному поведению.