Могут ли многоуровневые блокировки вызывать взаимоблокировки в многопоточных программах?

У меня есть программа с писателями и читателями, и их права доступа контролируются монитором.

Итак, это должно было голодать, но я зашел в тупик. Мне было интересно, почему, а потом я вспомнил, что поставил еще одну блокировку, которая, как мне кажется, была ненужной внутри моего метода чтения внутри читателей, чтобы защитить мою глобальную переменную от несоответствий. Я думал, что это не вызовет взаимоблокировки, потому что я мог запускать потоки 10000 раз без каких-либо взаимоблокировок, но когда мне пришлось выполнять лабораторную демонстрацию, я думаю, что она зашла в тупик на 10010-м потоке. Хотя я не понимаю, зачем это делать. Кроме того, я не ожидал, что он умрет с голоду, но, видимо, так и должно было быть.

Мой вопрос: ответственны ли эти многоуровневые блокировки за взаимоблокировку? Если нет, то чем это вызвано?!

    import java.io.*;
    import java.io.IOException;
    import java.util.*;

    public class Writer extends Thread{

    private int number;

    public Writer(int number)
    {
        this.number = number;
    }

    public int getNumber()
    {
        return number;
    }

        public static void Write(String filename){

        try {

            String content = RandomString();


            File f = new File(filename);

            if (!f.exists())
            {
                f.createNewFile();
            }


            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("Task1out.txt", true)));
            out.println(content);
            out.close();


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String RandomString(){

        String chars = new String("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
        int n = chars.length();

        String randomString = new String();
        Random r = new Random();

            for (int i=0; i<100; i++)
            {
                randomString = randomString + chars.charAt(r.nextInt(n));
            }

        System.out.println("RandomString() generated: " + randomString);

        return randomString;

    }



    public void run(){

        try{

        //FileControl fc = new FileControl();

            int number = this.getNumber();


            for(int i = 0; i <1000; i++) //CHANGE IT TO 1000
            {
                main.fc.WriterEntry(number);

                //write file random characters (must append)

                Write("Task1out.txt");

                main.fc.WriterExit(number);

            }
        } catch(InterruptedException e)
        {
            System.out.println("Interrupted Exception caught");
        }

    }


}

Это класс писателя.

    import java.io.BufferedWriter;
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.*;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;



public class Reader extends Thread{


    private int number;

    public Reader(int number)
    {
        this.number = number;
    }


    public int getNumber()
    {
        return number;
    }

        public static synchronized void Read(String filename)throws InterruptedException{

        BufferedReader br = null;





            main.lock.lock(); //lock
        try{




        try {


            String line;
            char[] chars = new char[100];
            int readIndex2 = 0;
            int addToIndex = 0;



            br = new BufferedReader(new FileReader(filename));


            int initialReadIndex = main.getIndex();




            System.out.println("initial read index: " + initialReadIndex);

            while ((line = br.readLine()) != null && readIndex2 < initialReadIndex+100 && addToIndex < 100) {

                for(int i = 0; i< 100; i++)
                {
                    if (initialReadIndex == readIndex2 || initialReadIndex < readIndex2)
                    {

                        if(line.length() > addToIndex)
                        {




                        chars[i] = line.charAt(i);
                        addToIndex++;
                        }


                    }
                    else
                    {
                        readIndex2++;
                    }
                }
                System.out.println(chars);
            }

            if(line == null)
            {
                System.out.println("nothing to read");
            }



            main.incrementIndex(addToIndex);


            System.out.println("current read index: " + (initialReadIndex + addToIndex));





        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("buffered reader exception");
        } finally {


            try {


                if (br != null)
                    {

                    br.close();
                    }
            } catch (IOException ex) {
                ex.printStackTrace();
                System.out.println("exception during closing");
            }
        }
        }finally{
            main.lock.unlock(); //lock

        }

        }


    public void run(){

        try{


        //FileControl fc = new FileControl();


        int number = this.getNumber();


            for(int i = 0; i <1000; i++) //CHANGE IT TO 1000
            {
                main.fc.ReaderEntry(number);

                //read file

                Read("Task1out.txt");

                main.fc.ReaderExit(number);
            }
        } catch(InterruptedException e)
        {
            System.out.println("Interrupted Exception caught");
        }

    }



        }

Это класс читателя.

 import java.io.BufferedWriter;
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    public class main{

    public static FileControl fc = new FileControl();

    final static Lock lock = new ReentrantLock();

    public static int readIndex;

    public static void incrementIndex(int increment) {


                readIndex = readIndex + increment;

    }

    public static int getIndex()
    {
        return readIndex;
    }



    public static void main(String[] args) throws InterruptedException {



            Writer [] writer = new Writer[10];
            Reader [] reader = new Reader[10];

            for(int i = 0; i < 10; i++)
            {
                reader[i] = new Reader(i);
                writer[i] = new Writer(i);
                //creating readers and writers

            }

            for(int i = 0; i < 10; i++)
            {
                //anonymous threads
                //(new Thread(new Writer())).start();
                //(new Thread(new Reader())).start();

                reader[i].start();
                writer[i].start();

            }




            for(int i = 0; i < 10; i++)
            {
                try{
                    reader[i].join();
                    writer[i].join();
                } catch(InterruptedException e){
                    e.printStackTrace();
                }


            }






        }

}

Это основной класс.

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;


    public class FileControl {
    final Lock lock = new ReentrantLock();
    final Condition writers = lock.newCondition();
    final Condition readers = lock.newCondition();
    int activereaders = 0;
    int waitingwriters = 0;
    boolean writing = false;

    public void WriterEntry(int number)throws InterruptedException{
        lock.lock();
        try{
                if(writing == true || activereaders > 0){
                    waitingwriters++;
                    System.out.println("Writer thread " + number + " : waiting to write");
                    writers.await();
                    waitingwriters--;
                }
                System.out.println("Writer thread " + number + " : ready to write");

                writing = true;
           }
        finally{
            lock.unlock();
        }


    }



    public void WriterExit(int number)throws InterruptedException{
        lock.lock();
        try{
            System.out.println("Writer thread " + number + " : finished to write");

            System.out.println("writers " + waitingwriters + "readers " + activereaders); //test

            if(waitingwriters > 0)
                writers.signal();
            else{
                writing = false;
                readers.signal();
            }
        }
        finally{
            lock.unlock();
        }

    }


    public void ReaderEntry(int number)throws InterruptedException{
        lock.lock();
        try{

            if(writing == true || waitingwriters > 0){ //remove activereaders > 0
                System.out.println("Reader thread " + number + " : waiting to read");
                readers.await();
                activereaders++;
            }


            System.out.println("Reader thread " + number + " : ready to read");
        }
        finally{
            lock.unlock();
        }

    }

    public void ReaderExit(int number)throws InterruptedException{
        lock.lock();
        try{



        activereaders--;



        System.out.println("Reader thread " + number + " : finished to read");

        System.out.println("writers " + waitingwriters + "readers " + activereaders); //test

            if(activereaders == 0)
            {
                if(waitingwriters > 0)
                {
                    writers.signal();
                }
                else
                {
                    readers.signal();
                }
            }
        }
        finally{
            lock.unlock();
        }

    }


}

Это монитор.

псевдокод для монитора


person user2967016    schedule 07.11.2013    source источник
comment
Я удивлен, что вы действительно получили ответы... Слишком много кода, на будущее, пожалуйста, старайтесь вводить только соответствующие части   -  person Maciej Cygan    schedule 08.11.2013


Ответы (2)


Всякий раз, когда у вас есть несколько блокировок A, B и C, у вас может возникнуть взаимоблокировка, если вы не гарантируете, что ваш код попытается получить указанные блокировки в том же порядке.

final Lock A = new ReentrantLock();
final Lock B = new ReentrantLock();
final Lock C = new ReentrantLock();

A,B,C или C,B,A или A,C,B - это не имеет значения, если порядок последователен.

Проблема возникает, когда у вас есть одна попытка кода для: A, B, C и другая попытка для C, B, A.

Как вы, вероятно, можете догадаться, поскольку A и C удерживаются, один из двух получит B, а затем оба зайдут в тупик. (Ака у вас есть цикл в графе блокировки ресурсов)

Формально взаимоблокировка может возникнуть только если выполняются все следующие условия:

  1. Без вытеснения: система не освобождает ресурсы после выделения; они могут быть освобождены только в процессе удержания.
  2. Циклическое ожидание: обсуждалось выше.
  3. Взаимное исключение: только один процесс может использовать ресурс в любой момент времени.
  4. Удержание ресурсов: в настоящее время процесс удерживает по крайней мере один ресурс и запрашивает/ожидает дополнительные ресурсы, которые удерживаются другим процессом.

Лучшее решение — убедиться, что порядок последователен, или зафиксировать его на более высоком (одиночном) уровне. Другим вариантом является использование библиотеки блокировки, которая истечет время ожидания при попытке блокировки (или используйте условия и напишите свою собственную оболочку, которая делает это). Но такой подход не для слабонервных. Некоторые реализации этого будут ждать случайное количество времени и пытаться снова, но это может быть крайне неэффективно по мере увеличения количества блокировок.

Ресурсы:

  • Вот практическая статья об анализе взаимоблокировок в Java, которая может вас заинтересовать: http://www.journaldev.com/1058/java-deadlock-example-and-how-to-analyze-deadlock-ситуация
  • Вы также можете использовать инструменты с открытым исходным кодом, такие как JCarder, чтобы найти взаимоблокировку: http://www.jcarder.org/, которая для программ с большими дампами может быть проще, чем пытаться копировать файлы дампа.

P.S. На самом деле я не читал большую часть вашего кода, так как он плохо отформатирован и не является минимальным примером (т.е. слишком многословен для наших целей здесь). Но этот совет должен ответить на ваш вопрос с теоретической точки зрения.

person voidlogic    schedule 07.11.2013
comment
Порядок должен был быть одинаковым, так как все они получают блокировку A, которая находится в общем, а затем блокировку B, которая находится в Readers. Так что заказ не мог быть причиной. Я очень озадачен тем, что вызвало тупик. - person user2967016; 08.11.2013

Это, конечно, возможно. Вы также можете проверить во время выполнения!

Первым шагом является получение дампа потока. Вот три метода:

  • Если вы откроете процесс в VisualVM и перейдете на вкладку «потоки», он сообщит вам, обнаружит ли он такой тупик. Затем вы можете сделать дамп потока (здесь есть кнопка), который сообщит вам, что делает каждый поток, а также любые блокировки, которыми он владеет, и любые блокировки (если таковые имеются), которые он пытается получить.
  • В Linux или Mac вы можете получить стек, введя kill -3 <pid>, где <pid> — идентификатор вашего Java-процесса. Он выгружает тот же дамп потока в stderr. Нижняя часть этого дампа потока также будет содержать сводку обнаруженных взаимоблокировок. Я не знаю, как это сделать в Windows.
  • Вы также можете вызвать jstack <pid>, который выведет дамп потока на стандартный вывод (стандартный вывод jstack, а не исходный процесс Java').

Я написал пример программы, которая блокируется, и запустил ее (см. мою суть). Соответствующий раздел дампа потока:

Found one Java-level deadlock:
=============================
"Thread-2":
  waiting for ownable synchronizer 7f42b0f38, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "Thread-1"
"Thread-1":
  waiting for ownable synchronizer 7f42ba170, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "Thread-2"

И соответствующие состояния потока:

"Thread-2" prio=5 tid=7fc01c911000 nid=0x113d18000 waiting on condition [113d17000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <7f30c3528> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:842)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1178)
    at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:186)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:262)
    at Locky$Boomer.run(Locky.java:22)
    at java.lang.Thread.run(Thread.java:680)

   Locked ownable synchronizers:
    - <7f30c3558> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

"Thread-1" prio=5 tid=7fc01d06c800 nid=0x113c15000 waiting on condition [113c14000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <7f30c3558> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:842)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1178)
    at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:186)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:262)
    at Locky$Boomer.run(Locky.java:22)
    at java.lang.Thread.run(Thread.java:680)

   Locked ownable synchronizers:
    - <7f30c3528> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

Это не будет работать для всех взаимоблокировок. Например, взаимоблокировки из-за ожидания внешних ресурсов не будут обнаружены. Но он будет обнаруживать взаимоблокировки на основе Lock, а также блокировки на основе synchronized.

person yshavit    schedule 08.11.2013