2016-10-08 2 views
0

Я хочу знать, разрешено ли исключать блокировки резьбы, не создавая ниток одновременно? Есть ли другой способ избежать взаимоблокировок в следующем коде?Предупреждение о взаимоблокировке тупика

Заранее благодарен!

public class ThreadDeadlocks { 

    public static Object Lock1 = new Object(); 
    public static Object Lock2 = new Object(); 

    public static void main(String args[]) { 

     ThreadDemo1 t1 = new ThreadDemo1(); 
     ThreadDemo2 t2 = new ThreadDemo2(); 

     t1.start(); 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException e) { 
     } 
     t2.start(); 
    } 

    private static class ThreadDemo1 extends Thread { 
     public void run() { 
      synchronized (Lock1) { 
       System.out.println("Thread 1: Holding lock 1..."); 
       try { 
        Thread.sleep(10); 
       } catch (InterruptedException e) { 
       } 
       System.out.println("Thread 1: Waiting for lock 2..."); 
       synchronized (Lock2) { 
        System.out.println("Thread 1: Holding lock 1 & 2..."); 
       } 
      } 
     } 
    } 

    private static class ThreadDemo2 extends Thread { 
     public void run() { 
      synchronized (Lock2) { 
       System.out.println("Thread 2: Holding lock 2..."); 
       try { 
        Thread.sleep(10); 
       } catch (InterruptedException e) { 
       } 
       System.out.println("Thread 2: Waiting for lock 1..."); 
       synchronized (Lock1) { 
        System.out.println("Thread 2: Holding lock 1 & 2..."); 
       } 
      } 
     } 
    } 
} 
+0

Как правило, 'sleep()' никогда не может _fix_ проблему параллелизма. Что он может сделать, это может сделать проблему гораздо менее вероятной. Это нормально для демонстраций, обучения и личного использования; но если вы строите программное обеспечение для производства, то, что он переводит, это превращает ошибку, которая была бы поймана при тестировании на ошибку, которая не будет происходить до тех пор, пока месяцы или годы спустя, на сайте клиента, и которые к тому времени, будет практически невозможно диагностировать. –

ответ

0

Нет, начальные потоки в разное время - это не способ избежать взаимоблокировок - на самом деле то, что вы пытаетесь использовать с разными временами запуска, - это эвристика для сериализации их критических разделов. ++ понять, почему на и этот ответ

[Edited раствором]

Есть ли другой способ, чтобы избежать тупиков в следующем коде?

Самый простой способ приобрести замки в том же порядке на обеих нитей

synchronized(Lock1) { 
    // do some work 
    synchronized(Lock2) { 
    // do some other work and commit (make changes visible) 
    } 
} 

Если логика вашего кода диктует вы не можете сделать это, а затем использовать java.util.concurrent.locks классы. Например

ReentrantLock Lock1=new ReentrantLock(); 
ReentrantLock Lock2=new ReentrantLock(); 

private static class ThreadDemo1 extends Thread { 
    public void run() { 
     while(true) { 
      Lock1.lock(); // will block until available 
      System.out.println("Thread 1: Holding lock 1..."); 
      try { 
       // Do some preliminary work here, but do not "commit" yet 
       Thread.sleep(10); 
      } catch (InterruptedException e) { 
      } 
      System.out.println("Thread 1: Waiting for lock 2..."); 
      if(!Lock2.tryLock(30, TimeUnit.MILLISECOND)) { 
       System.out.println("Thread 1: not getting a hold on lock 2..."); 

       // altruistic behaviour: if I can't do it, let others 
       // do their work with Lock1, I'll try later 
       System.out.println("Thread 1: release lock 1 and wait a bit"); 
       Lock1.unlock(); 
       Thread.sleep(30); 
       System.out.println("Thread 1: Discarding the work done before, will retry getting lock 1"); 

      } 
      else { 
       System.out.println("Thread 1: got a hold on lock 2..."); 
       break; 
      } 
     } 
     // if we got here, we know we are holding both locks 
     System.out.println("Thread 1: both locks available, complete the work"); 
     // work... 
     Lock2.unlock(); // release the locks in the reverse... 
     Lock1.unlock(); // ... order of acquisition 

    } 
} 

// do the same for the second thread 

++ Чтобы продемонстрировать, почему задержки при запуске нити в разное время не является несложным решением, подумайте, если вы можете себе позволить, чтобы задержать одну из нитей на 10 секунд в примере ниже. Тогда подумайте, что вы будете делать , если вы действительно не знаете, как долго ждать.

private static class ThreadDemo1 extends Thread { 
    public void run() { 
     synchronized (Lock1) { 
      System.out.println("Thread 1: Holding lock 1..."); 
      try { 
       // modelling a workload here: 
       // can take anywhere up to 10 seconds 
       Thread.sleep((long)(Math.random()*10000)); 
      } catch (InterruptedException e) { 
      } 
      System.out.println("Thread 1: Waiting for lock 2..."); 
      synchronized (Lock2) { 
       System.out.println("Thread 1: Holding lock 1 & 2..."); 
      } 
     } 
    } 
} 

private static class ThreadDemo2 extends Thread { 
    public void run() { 
     synchronized (Lock2) { 
      System.out.println("Thread 2: Holding lock 2..."); 
      try { 
       // modelling a workload here: 
       // can take anywhere up to 10 seconds 
       Thread.sleep((long)(Math.random()*10000)); 
      } catch (InterruptedException e) { 
      } 
      System.out.println("Thread 2: Waiting for lock 1..."); 
      synchronized (Lock1) { 
       System.out.println("Thread 2: Holding lock 1 & 2..."); 
      } 
     } 
    } 
} 
+0

Как изменить методы запуска на обоих классах ThreadDemo, чтобы избежать взаимоблокировок? – peter59

+0

@ peter59 См. Мой обновленный ответ. –

+0

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

2

Есть два способа получить тупик:

  1. блокировки эскалации. Например, поток, содержащий доступный для чтения считыватель , пытается перерасти в эксклюзивную блокировку записи. Если более одного поток, удерживающий блокировку чтения, пытается перерасти в блокировку записи, результаты тупика . Это не относится к тому, что вы делаете. (Оффлайн, я даже не знаю, возможно ли для эскалации блокировки на Java.)
  2. Unspecified lock order. Если поток A блокирует объект 1, затем пытается заблокировать объект 2, тогда как поток B блокирует объект 2, затем пытается заблокировать объект 1, может возникнуть взаимоблокировка. Это именно то, что вы делаете.

Это единственный способ зайти в тупик. Каждый сценарий взаимоблокировки дойдет до одного из них.

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

Это единственные способы предотвратить взаимоблокировки. Обезвреживание с таймингами с задержкой не гарантировано.

+0

Если я не установил задержку между двумя потоками, у меня есть тупик, подобный этому: Резьба 1: Удерживание блокировки 1 ... Резьба 2: Удерживание блокировки 2 ... Резьба 2: Ожидание блокировки 1. .. Тема 1: Ожидание блокировки 2 ... Итак, следует ли мне изменять методы запуска обоих классов ThreadDemo или могу ли я оставить задержку между двумя запусками этих потоков на главном? – peter59

+0

@ peter59 Любая задержка не гарантируется. * Только * гарантированный способ избежать тупиковой ситуации при отсутствии эскалации блокировки - всегда получать блокировки в том же порядке. Это так просто. –

+0

Итак, как мне сделать, чтобы всегда получать блокировки в том же порядке? – peter59

1

Как уже упоминалось, задержки не помогут, потому что потоки по своей природе имеют неизвестное время начала. Когда вы вызываете start() в потоке, он становится runnable, но вы не можете знать, когда он будет запущен.

1

Я предполагаю, что это всего лишь демо-код, поэтому вы уже знаете, что игра со снами не гарантируется работать (как подчеркивалось в других ответах).

В демо-код, который я вижу два варианта, чтобы попытаться избежать тупиковой ситуации:

  • Удалить сон в теле функций, выполняемых потоками и просто поставить один, достаточно долго, спать между начало двух потоков; с практической точки зрения, это должно дать достаточно времени для того, чтобы первый поток был запланирован и завершил его работу, а затем второй поток получит оба замка без конкуренции. Но вы уже знаете, что планирование политики не находится под вашим контролем, и это не гарантируется вообще.

  • ли приобретать замки в том же порядке, в обеих нитях, без использования какого-либо сна вообще, то есть

    synchronized (Lock1) { 
        synchronized (Lock2) { 
         // ... 
        } 
    } 
    

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

UPDATE:

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

Говорят, что у нити есть замок между временем, когда он приобрел замок и освободил замок. Пока поток владеет блокировкой, ни один другой поток не может получить тот же замок. Фактически, другой поток будет блокироваться, когда он попытается получить тот же замок.

Каждый объект в Java имеет связанный с ним встроенный замок. Синхронизированный оператор позволяет автоматически получить внутреннюю блокировку указанного объекта и освободить его после выполнения кода.

+0

Я пробовал ваш второй вариант, и он работает, спасибо, но я не понимаю, почему первый поток блокирует второй во время его завершения? – peter59

+0

Хорошо, спасибо за ваше объяснение, так что это все благодаря синхронизированному утверждению? – peter59

Смежные вопросы