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 на все виды исключений не помогает - очевидно, что там нет исключений.
Огромная просьба к матерым спецам многопоточности помочь выловить проблему или предложить более надежное решение задачи (быстродействием можно принебречь).
Спасибо заранее!