Почему, похоже, для этого синхронизированного блока требуется много времени, чтобы получить блокировку?

Я новичок в многопоточности в java, и у меня есть вопрос, который может показаться тривиальным.

Мне нужно отлаживать стороннюю часть кода, и мне нужна какая-то основная информация, чтобы знать, где искать проблему, потому что код очень большой.

Когда выполняется следующий код:

public void method()
{
   long startTime = System.currentTimeMillis();
   synchronized (obj)
   {
      log( "time:" + System.currentTimeMillis() - startTime + " ms" );
      ...
   }
}

Я получаю:

11:13:12 - time: 3816 ms
...
11:14:14 - time: 0 ms

Почему занимает так много времени (3816 мс), чтобы получить блокировку для объекта? Где я должен смотреть? Например, я бы предположил, что возможным ответом будет поиск кода, который получает блокировку для "obj", то есть блока, например:

synchronized (obj) { ... }

Или возможно, что любая модификация объекта obj без "синхронизированного" может также блокировать объект?

Ответ 1

Если для получения блокировки требуется поток, который длится, потому что кто-то еще удерживает его.

Вы должны искать две вещи:

  • Кодовые блоки, которые synchronize на одном и том же объекте или на других ссылках на него (известный как синхронизированные инструкции):

    synchronized (obj) {
     ... 
    }
    
  • Синхронизированные методы внутри самого объекта.

    Скажем obj имеет тип MyObject, тогда вы должны искать такие методы, как:

    public class MyObject{
        public synchronized void myMethod() {
         ...
        }
    }
    

    Поскольку они по существу совпадают с

    public class MyObject{
        public void myMethod() {
            synchronized (this) {
             ...
            }
        }
    }
    

    Итак, если поток выполняет obj.myMethod(), потоки, которые хотят ввести блок synchronized (obj), должны будут ждать, поскольку все они блокируются на одном и том же объекте. Это, кстати, является причиной того, что я настоятельно рекомендую никогда не использовать синтаксис синхронизированного метода и всегда блокировать доступ к частному (или защищенному) классу.

Если другой поток в настоящее время выполняет код внутри такого блока, текущий поток будет заблокирован до тех пор, пока не закончится другой поток.

Вы можете использовать jvisualvm вкладка Темы или Jstack делать снимки текущего состояния выполнения всех потоков и блокировок, которые они удерживают. Если вы находитесь на android, см. этот ответ на вопрос о том, как получить дамп потока там.

Ответ 2

Утилита jstack, которая является частью jdk, может помочь в этом. Опция -l (длинный список) будет печатать все блокировки, принадлежащие различным потокам. Если вы можете поймать свою программу посреди проблемы, вы можете найти другой поток, удерживающий блокировку. Вы делаете это, находя свой поток, видя, какой объект условия он ожидает, затем просматривая остальные трассировки стека для этого объекта условия.

Этот article содержит более подробную информацию о том, как посмотреть дамп потока.

Ответ 3

Вам необходимо проверить следующее:

  • Есть ли какой-либо метод/блок в вашем классе obj, который синхронизируется с этим. Если да, то должно быть несколько потоков, где один из них - ваш вышеприведенный код, в то время как другие могут использовать методы того же obj.
  • Где вы все обмениваетесь? Если он разделен несколькими классами, то проверьте, кто заперт на том же объекте.