I have used a Singleton to implement a cache with lazy loading. Instead of synchronizing the read/write methods I have used a sychronizedMap to store the values. My implementation is actually a cache of cache. The DictionaryObject are read-only objects and the singleton CacheManager stores a cache of such objects.
To handle updates I simply register certain types of DictionaryObject( identified by those implementing an interface actually) with a CacheMonitor. The monitor is a daemon
thread that expires the cache once in a while and reloads the data from the database.
As for the business layer, the SBs and EBs read the data through cache manager. The cache manager exposes methods such as -
DictionaryObject getValue(ClassType, id)
Collection getValues(classType)
that hide the fact every DictionaryObject has its own cache.
Hope this helps.