1

EDIT:

This question is about resolving a problem using only Java code. The problem is caused by SQLLite indirectly but cannot be solved by changing Database system or with SQL code. I mention SQLLite because otherwise users would point to useless solutions that actually breaks project imposed requirments (A defined user interface and behaviour and SQLite as DBMS because it can run without server and projects are subject to automatic correction).

EDIT2: The deadlock happens on Java side, it is not easy to see it, I debugged for a whole day before realizing that, forget about SQLLite, I need to find a way to get Parking working like a monitor but without conflicting with synchronized causing a deadlock


Currently I have the following situation:

I Have a simplified Parking class, actually it is a monitor where clients call the lendVehicle method from other threads.

public class Parking{

    private final long       parkId;
    private final ParkingAPI sqlLayer;
    private final Lock       lock = new ReentrantLock();
    private final Condition  notEmpty = lock.newCondition();

    public Parking( long mparkId, ParkingAPI api){
        sqlLayer = api;
        parkId = mparkId;
    }

    long lendVehicle(){
        lock.lock();
        try{
            while(sqlLayer.countVehicles(parkId) == 0)
                notEmpty.await();

            return sqlLayer.lend(parkId);

        } finally{
            lock.unlock();
        }
    }

    void giveBackVehicle(long vehicleId){
        lock.lock();
        try{
            sqlLayer.giveBack(vehicleId,parkId);
            notEmpty.signal();

        } finally{
            lock.unlock();
        }
    }

When I mock the SQL layer with just an atomic counter, the class works perfectly, however since the application is using SQL Lite I have to protect the connection from concurrent access (basically I can execute 1 query at any given time because of SQL Lite).

Currently the code is synchronized over the DBLayer object (wich is shared across all classes).

class ParkingQuery implements ParkingAPI{

    private final DBLayer connection;

    public SQLLayer(DBLayer db){
        connection = db;
    }

    @Override
    int lend(long parkId){
        synchronized( connection){
            return connection.lendVehicleFromPark(parkId);
        }
    }

    @Override
    int countVehicles(long parkId){
        synchronized( connection){
            return connection.countVehiclesQuery(parkId);
        }
    }

    @Override
    void giveBack(long vehicleId, long parkId){
        synchronized( connection){
            connection.giveBackVehicle(parkId, vehicleId);
        }
    }
}

The problem is the synchronized part, wich does not play well with the Parking's monitor: wich in fact cause a deadlock.

How can I preserve functionality of parking? (Can't remove synchronized on ParkingQuery because SQLite just explode if queries are not synchronized and bad things starts happening).

Note that concurrent access to SQLLite is mandatory because that's a school project.

EDIT: Desired parking behaviour: If a user desire lending a vehicle and it is not available the user have to wait someone else returns back a vehicle from lending.

user4157124
  • 2,809
  • 13
  • 27
  • 42
CoffeDeveloper
  • 7,961
  • 3
  • 35
  • 69
  • If Parking is the only class that uses ParkingAPI to update the database, I'd say you don't need to synchronize both. The SQLite query is inside a synchronized block, so it's thread safe. I don't see why synchronized keyword is insufficient for Parking. I don't like your naming. ParkingAPI? I'd expect ParkingDAO or ParkingRepository. – duffymo Sep 06 '15 at 12:18
  • Apart naming that comes from the domain scope, If 2 users need to lend from same parking and only 1 vehicle available, the wanted behaviour is that one user will wait for a vehicle becoming avaialable, without the condition variable both will see vehicle count == 1 and proceed with lending query wich may have hard to predict side-effects. – CoffeDeveloper Sep 06 '15 at 12:23
  • and of course it is not the only class there are other things like registration, login, adding new parkings and vehicles, everything work except lending with Condition variables. – CoffeDeveloper Sep 06 '15 at 13:27
  • Sounds like SQLite is a requirement. Too bad - I don't think you'd have as many issues with MySQL or PostgreSQL. Might be worth a try to swap out the database and see if things improve. – duffymo Sep 06 '15 at 14:12
  • I cannot change the database (school required, if you read the question at least). I see no reason why SQL answer can anaswer a java concurrency question. I mention SQLlite because that's indirectly causing my problem. – CoffeDeveloper Sep 06 '15 at 14:25
  • Why you mark a duplicate to a total unrelated topic? the problem here is Java concurrency (I mention SQLite because otherwise I would get totally useless answers) – CoffeDeveloper Sep 06 '15 at 14:32
  • Total [sic] unrelated topic? You've got SQLite deadlock problems. – duffymo Sep 06 '15 at 14:34
  • Deadlock on java side sir, please read the question, ever used Java condition variables or "synchronized" key word? – CoffeDeveloper Sep 06 '15 at 14:38
  • Where does it deadlock at? It can't possibly be the database lock because that only waits for one resource. Does `lendVehicleFromPark` block if the vehicle is already lended? – Colonel Thirty Two Sep 06 '15 at 15:03
  • I also thinked the database lock should not cause any deadlock, but the deadlock/livelock happens only If I use the database lock :/ – CoffeDeveloper Sep 06 '15 at 15:20
  • 1
    Your problem would be easy to solve without all this nonsense if you'd use a BlockingDeque and producer/consumer: http://javarevisited.blogspot.com/2012/02/producer-consumer-design-pattern-with.html – duffymo Sep 06 '15 at 18:16

1 Answers1

2

This code:

long lendVehicle(){
        lock.lock();
        try{
          ...
    }

is wrong. You basically lock then it is not guaranteed to unlock because it is outside of the try block. Try to break down the problem more appropriately. You have a producer, which is the giving back of the car and then your consumer is lending the car. So you know that your "car lot" only can hold N cars (lets say 10 for example sake). So if your buffer (car lot) is full you don't want to enable more cars to be given back (think about trying to park in a garage where there are no spots left).

Now you can simply check in your return car function if the buffer is full and if it is ignore the operation. When lending the car you want to make sure the lot is not empty.

Woot4Moo
  • 23,987
  • 16
  • 94
  • 151