You are here: Advanced Features > Concurrency and db4o > Example

Sharing an Object Container Across Threads

You want to share an object container across different threads? Then you automatically also share the stored objects across threads, due to the reference-cache. Therefore you need to synchronize the access to your object-model.

The basic access-pattern should be like this:

  1. Access the lock which protects the data model.
  2. Do manipulations on the data model, which may involve operations on the object container.
  3. Release the lock.

A Small Example

For example we want to do some back-ground updates, while the rest of the application carries on. As soon as we have this kind pattern we need to protect the access to the data model.

For example this operation starts a background task and carries on doing other things:

// Schedule back-ground tasks
final Future<?> task = executor.submit(new Runnable() {
    @Override
    public  void run() {
        updateSomePeople(container);
    }
});
// While doing other work
listAllPeople(container);
LockingOperations.java: Schedule back-ground tasks

Unfortunately these two tasks do work on the same data model, which can lead to race conditions. Therefore access to the data model has to be protected.

private  void updateSomePeople(ObjectContainer container) {
    synchronized (dataLock) {
        final ObjectSet<Person> people = container.query(new Predicate<Person>() {
            @Override
            public  boolean match(Person person) {
                return person.getName().equals("Joe");
            }
        });
        for (Person joe : people) {
            joe.setName("New Joe");
            container.store(joe);
        }
    }
}
LockingOperations.java: Grab the lock protecting the data
private  void listAllPeople(ObjectContainer container) {
    synchronized (dataLock) {
        for (Person person : container.query(Person.class)) {
            System.out.println(person.getName());
        }
    }
}
LockingOperations.java: Grab the lock to show the data

Improving the Locking

Of course the locking showed above is very course grained. A simple improvement would be to use read-write locks. In the end you need to adapt the locking strategy to your application.

Creating a Transaction Model

One model which works good for this scenario is to create a transaction abstraction to do your operations on the data model. Then you do all operations in such a transaction. The transaction manages the lock and the db4o transaction. Such an implementation could look like this:

public <T> T inTransaction(TransactionFunction<T> transactionClosure) {
    synchronized (lock) {
        try {
            return transactionClosure.inTransaction(database);
        } catch (Exception e) {
            database.rollback();
            throw  new TransactionFailedException(e.getMessage(), e);
        } finally {
            database.commit();
        }
    }
}
DatabaseSupport.java: A transaction method

And then we can use this transaction method when accessing our data model.

private  void listAllPeople(DatabaseSupport dbSupport) {
    dbSupport.inTransaction(new TransactionAction() {
        @Override
        public  void inTransaction(ObjectContainer container) {
            final ObjectSet<Person> result = container.query(Person.class);
            for (Person person : result) {
                System.out.println(person.getName());
            }
        }
    });
}
TransactionOperations.java: Use transaction for reading objects
private  void updateAllJoes(DatabaseSupport dbSupport) {
    dbSupport.inTransaction(new TransactionAction() {
        @Override
        public  void inTransaction(ObjectContainer container) {
            final ObjectSet<Person> allJoes = container.query(new Predicate<Person>() {
                @Override
                public  boolean match(Person person) {
                    return person.getName().equals("Joe");
                }
            });
            for (Person joe : allJoes) {
                joe.setName("New Joe");
                container.store(joe);
            }
        }
    });
}
TransactionOperations.java: Use transaction to update objects

Remember that this is only an example. You can use other techniques like annotations or aspects to implement the right behavior. And you also can use more sophisticated locks, like read write locks. The only thing which is important is that you synchronize the access your shared data objects.

Alternatives

Alternatively you can avoid sharing data objects and rather use multiple object container to manage concurrent data access.