ReentrantLock не всегда справляется с блокировкой общего ресурса


Имеем задачу следующего содержания: есть порт, в порту - несколько доков, к которым причаливают корабли (у одного дока может стоять на погрузке/выгрузке только 1 корабль в какой-то момент времени). Получается, у нас есть 2 разных общих ресурса: доки - один корабль блокирует док, когда выгружается/загружается товар, и склад порта - корабль в доке на время погрузки/выгрузки блокирует склад порта.
Есть код главного метода:

public static void main(String[] args) {
        Port barcelona = new Port(10000, 100);
        barcelona.addContainersToWarehouse(5000);
        ExecutorService service = Executors.newFixedThreadPool(barcelona.getDockQuantity());
        for (int i = 0; i < 250; i++) {
            service.submit(new Ship(barcelona, 10));
        }
        service.shutdown();
    }

Есть класс Port

public class Port {                 
    private final int capacity;
    private final BlockingDeque<Container> warehouse;
    private final List<Dock> docks;
    private Lock lock;

    public Port(int capacity, int dockQuantity) {
        this.capacity = capacity;
        this.warehouse = new LinkedBlockingDeque<>(capacity);
        this.docks = new ArrayList<>(dockQuantity);
        for (int i = 1; i <= dockQuantity; i++) {
            docks.add(new Dock(i));
        }
        this.lock = new ReentrantLock();
    }

    public synchronized BlockingDeque<Container> getWarehouse() {
        return warehouse;
    }

    public int getDockQuantity() {
        return this.docks.size();
    }

    public List<Dock> getDocks() {
        return docks;
    }

    public void addContainersToWarehouse(int containerQuantity) {
        for (int j = 0; j < containerQuantity; j++) {
            try {
                warehouse.putFirst(new Container());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized Lock getLock() {
        return lock;
    } 
}

Есть класс Dock

public class Dock {
    private final int dockId;
    private final Lock lock;

    public Dock(int id) {
        this.dockId = id;
        this.lock = new ReentrantLock();
    }

    public Lock getLock() {
        return lock;
    }
}

Есть класс-поток Ship

 public Ship(Port port, int capacity) {
        this.port = port;
        this.shipId = new Random().nextInt(1000);
        this.flag = false;
        this.capacity = capacity;
        this.containers = new LinkedBlockingDeque<>(capacity);
        for (int i = 0; i < (capacity * new Random().nextInt(capacity) / 10); i++) {
            containers.offer(new Container());
        }
    }


    public void run() {
        for (Dock dock : port.getDocks()) {
            if (dock.getLock().tryLock()) {
                while (!flag) {
                    System.out.println("Ship " + shipId + " with " + containers.size() + " containers approached to  " + dock.toString());
                    shipUnload(port);
                    System.out.println("Ship " + shipId + " has unloaded containers and got " + containers.size() + " containers abroad now");
                    System.out.println("Port's warehouse got - " + port.getWarehouse().size() + " containers");
                    shipLoad(port);
                    System.out.println("Ship " + shipId + " has loaded and got " + containers.size() + " containers abroad now");
                    System.out.println("Port's warehouse got - " + port.getWarehouse().size() + " containers");
                    flag = true;
                    System.out.println("Ship " + shipId + " leaves " + dock.toString());
                }
                dock.getLock().unlock();
            }
        }
    }

    public void shipUnload(Port port) {
        try {
            if (port.getLock().tryLock()) {
                while (!(port.getWarehouse().remainingCapacity() == 0) && (containers.size() > 0)) {
                    try {
                        port.getWarehouse().putFirst(containers.takeFirst());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            port.getLock().unlock();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }

    public void shipLoad(Port port) {
        try {
            if (port.getLock().tryLock()) {
                while (!(containers.remainingCapacity() == 0) && (port.getWarehouse().size() > 0)) {
                    try {
                        containers.putFirst(port.getWarehouse().takeLast());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            port.getLock().unlock();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }
}

Так теперь самое главное - в чем же проблема, почему, как мне кажется, ReentrantLock не справляется?
На небольших объемах - для данного кода - это 10-100 кораблей и 10-30 доков, все работает великолепно.
Если же использовать, например, 250 кораблей и 100 доков (как в коде выше), то иногда (1-5 раз на все время исполнения кода) можно получить java.lang.IllegalMonitorStateException со следующим stacktrace:

java.lang.IllegalMonitorStateException at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457) at javathreads.model.Ship.shipUnload(Ship.java:56) at javathreads.model.Ship.run(Ship.java:31) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)

Своим умом могу предложоть, что это вызвано тем, что метод shipLoad() или shipUnload() выполняют port.getLock().unlock(); когда у них нет блокировки на общий ресурс port.warehouse, т.е. состояние замка у Port.lock = unlocked.
Но понять первопричину - не удается! Как так может быть, что метод потока выполняем код при условии, что ресурс заблокирован потоком, но в конце выполнения потока оказывается, что ресурс каким-то образом разблокирован? Установка блока try-catch на все виды исключений не помогает - очевидно, что там нет исключений.
Огромная просьба к матерым спецам многопоточности помочь выловить проблему или предложить более надежное решение задачи (быстродействием можно принебречь).
Спасибо заранее!


Ответы (0 шт):