Санкт-Петербургская группа тестирования JVM


« Построение native-ко... | Main | Open Source JDK »
20061006 пятница Октябрь 06, 2006

Разрешение взаимоблокировки

Интересной особенностью взаимоблокировки в Java является то, что участвующие в ней потоки невозможно остановить, то есть если в приложении возникает взаимоблокровка и приложение  каким-либо способом смогло её обнаружить, оно ничего не сможет с этим поделать, хотя возможно было бы, например, завершить один из блокированных потоков и тем самым дать возможность всем остальным продолжить работу. 

Проблема в том, что невозможно завершить поток, который пытается получить блокировку на объект, входя в synchronized метод или блок. Однако с появлением в Java новых способов синхронизации помимо  synchronized методов и блоков, а именно пакета java.util.concurrent, ситуация улучшилась, так-как  с помощью Thread.stop() возможно завершить выполнение потока, который пытается получить блокировку используя java.util.concurrent.Lock.lock(), а если для получения блокировки используется  java.util.concurrent.Lock.lockInterruptibly() то можно просто прервать это метод,  вызвав Thread.interrupt(), и при этом все потоки останутся живы!  

Рассмотрим небольшой пример, в котором Java программа обнаруживает и ликвидирует взаимоблокировку используя Thread.stop() (и заодно проверим, что monitoring API в Java 6.0 умеет находить  взаимоблокировки созданные с помощью java.util.concurrent.Lock). Применяя Thread.stop(), нельзя забывать, что это метод не рекомендуется использовать и прежде чем его вызывать стоит хорошо обдумать все последствия. 

 // Deadlock.java

import java.lang.management.*;
import java.util.concurrent.locks.*;

public class Deadlock {

    // два ресурса для создания взаимоблокировки:

    final static Object resource1 = new Object();
    static boolean fisrtResourceLocked;

    final static ReentrantLock resource2 = new ReentrantLock();
    static boolean secondResourceLocked;

    // два потока, которые захватывают ресурсы в разной последовательности:

    // DeadlockMakerThread1 сначала захватывает resource1, потом пытается захватить resource2
    static class DeadlockMakerThread1 extends Thread {

        DeadlockMakerThread1(String name) {
            super(name);
        }

        public void run() {
            synchronized (resource1) {
                System.out.println(getName() + ": ресурс захвачен: " + resource1);
                fisrtResourceLocked = true;

                while (!secondResourceLocked) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(getName() + ": пытаюсь захватить " + resource2);
                resource2.lock();
                System.out.println(getName() + ": ресурс захвачен: " + resource2);
            }
        }
    }

    // DeadlockMakerThread2 сначала захватывает resource2, потом пытается захватить resource1
    static class DeadlockMakerThread2 extends Thread {

        DeadlockMakerThread2(String name) {
            super(name);
        }

        public void run() {
            resource2.lock();
            System.out.println(getName() + ": ресурс захвачен: " + resource2);
            secondResourceLocked = true;

            while (!fisrtResourceLocked) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(getName() + ": пытаюсь захватить " + resource1);
            synchronized (resource1) {
                System.out.println(getName() + ": ресурс захвачен: " + resource1);
            }
        }
    }

    // пытаемся обнаружить взаимоблокировку с помощью метода ThreadMXBean.findDeadlockedThreads()
    public static void showDeadlock() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] ids = threadMXBean.findDeadlockedThreads();
        if (ids != null) {
            System.out.print("Обнаружена взаимоблокировка, идентификаторы потоков: ");
            for (long id : ids)
                System.out.print(id + " ");
        } else {
            System.out.println("Взаимоблокировка не обнаружена");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Thread thread1 = new DeadlockMakerThread1("Поток1");
        Thread thread2 = new DeadlockMakerThread2("Поток2");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        showDeadlock();

        // остановливаем поток, который блокирован вызовом ReentrantLock.lock()
        System.out.println("Останавливаем поток: " + thread1.getName());
        thread1.stop();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {

        }
        showDeadlock();

        System.exit(0);
    }
}
 Запускаем:

java Deadlock
Поток1: ресурс захвачен: java.lang.Object@de6ced
Поток2: ресурс захвачен: java.util.concurrent.locks.ReentrantLock@c17164[Locked by thread Поток2]
Поток2: пытаюсь захватить java.lang.Object@de6ced
Поток1: пытаюсь захватить java.util.concurrent.locks.ReentrantLock@c17164[Locked by thread Поток2]
Обнаружена взаимоблокировка, идентификаторы потоков: 9 8
Останавливаем поток: Поток1
Поток2: ресурс захвачен: java.lang.Object@de6ced
Взаимоблокировка не обнаружена
Как видно из полученного результата Поток2 после остановки Потока1 смог продолжить выполнение и взаимоблокировка исчезла! В этом примере даже метод Thread.getState() вернёт разные значения для потоков участвующих в блокировке: для Потока1, вызывающего Lock.lock()—ThreadState.WAITING, а для Потока2, который пытается войти в synchronized блок—ThreadState.BLOCKED. То есть с точки зрения виртуальной машины потоки находятся в разных состояниях, это связано с тем, что в этих двух случаях используются разные механизмы синхронизации. В частности реализация java.util.concurrent.Lock.lock() напрямую использует возможности  синхронизации, предоставляемые операционной системой (я думаю это ещё одна причина узнать больше о  пакете java.util.concurrent и, может быть, начать им пользоваться).     

Возможно, кроме этого достаточно грубого способа разрешения взаимоблокировки в следующем релизе Java появится более мощная поддержка борьбы с взаимоблокировками, например, в потоках, участвующих во взаимной блокировке могло бы быть выброшено исключение и проложение, перехватив его, могло бы разрешить  возникшую проблему(возможность такого поведения уже упоминается в описании java.util.concurrent.Lock.lock()).

опубликовал vmrobot ( окт 06 2006, 02:34:00 PM MSD ) Permalink Комментарии [1]

Trackback URL: http://blogs.sun.com/vmrobot/entry/%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B2%D0%B7%D0%B0%D0%B8%D0%BC%D0%BE%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8
Комментарии:

Ссылки поправлены. Спасибо, Blazkowicz.

опубликовал Kirill Shirokov Январь 23, 2007 at 07:05 PM MSK #

Опубликовать комментарий:

Имя
E-Mail:
URL:

Ваш комментарий:

HTML Syntax: Отключен

Хиты страниц за сегодня: 64