Greetings, fellow SO users.
I am currently in the process of writing a class of which instances will serve as a cache of JavaBean PropertyDescriptors
. You can call a method getPropertyDescriptor(Class clazz, String propertyName)
which will return the appropriate PropertyDescriptor
. If it wasn't retrieved previously, the BeanInfo
instance for the class is obtained and the right descriptor located. This result is then stored for the class-name pair so the next time it can be returned right away without the lookup or requiring the BeanInfo
.
A first concern was when multiple invocations for the same class would overlap. This was simply solved by synchronizing on the clazz parameter. So multiple invocations for the same class are synchronized, but invocations for a different class can continue unhindered. This seemed like a decent compromise between thread-safety and liveness.
Now, it is possible that at some point certain classes, which have been introspected, might need to be unloaded. I can't simply keep references to them since this might result in a classloader leak. Also, the Introspector
class of the JavaBeans API mentions that classloader destruction should be combined with a flush of the introspector: http://download.oracle.com/javase/6/docs/api/java/beans/Introspector.html
So, I've added a method flushDirectory(ClassLoader cl)
that will remove any class from the cache and flush it from the Introspector (with Introspector.flushFromCaches(Class clz)
) provided it was loaded with that classloader.
Now I have a new concern regarding synchronization. No new mappings should be added to the cache while this flush is in progress, while the flush should not start if access is still going. In other words, the basic problem is:
How do I make sure one piece of code may be run by multiple threads while another piece of code can only be run by one thread and prohibits those other pieces from running? It is a sort of one-way synchronization.
First I tried a combination of a java.util.concurrent.Lock
and an AtomicInteger
to keep count of the number of invocations in progress, but noticed that a lock can only be obtained, not checked if it currently is in use without locking. Now I'm using simple synchronization on an Object
over the atomic integer. Here's a trimmed-down version of my class:
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class DescriptorDirectory {
private final ClassPropertyDirectory classPropertyDirectory = new ClassPropertyDirectory();
private final Object flushingLock = new Object();
private final AtomicInteger accessors = new AtomicInteger(0);
public DescriptorDirectory() {}
public PropertyDescriptor getPropertyDescriptor(final Class<?> clazz, final String propertyName) throws Exception {
//First incrementing the accessor count.
synchronized(flushingLock) {
accessors.incrementAndGet();
}
PropertyDescriptor result;
//Synchronizing on the directory Class root
//This is preferrable to a full method synchronization since two lookups for
//different classes can never be on the same directory path and won't collide
synchronized(clazz) {
result = classPropertyDirectory.getPropertyDescriptor(clazz, propertyName);
if(result == null) {
//PropertyDescriptor wasn't loaded yet
//First we need bean information regarding the parent class
final BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(clazz);
} catch(final IntrospectionException e) {
accessors.decrementAndGet();
throw e;
//TODO: throw specific
}
//Now we must find the PropertyDescriptor of our target property
final PropertyDescriptor[] propList = beanInfo.getPropertyDescriptors();
for (int i = 0; (i < propList.length) && (result == null); i++) {
final PropertyDescriptor propDesc = propList[i];
if(propDesc.getName().equals(propertyName))
result = propDesc;
}
//If no descriptor was found, something's wrong with the name or access
if(result == null) {
accessors.decrementAndGet();
//TODO: throw specific
throw new Exception("No property with name \"" + propertyName + "\" could be found in class " + clazz.getName());
}
//Adding mapping
classPropertyDirectory.addMappin开发者_如何学运维g(clazz, propertyName, result);
}
}
accessors.decrementAndGet();
return result;
}
public void flushDirectory(final ClassLoader cl) {
//We wait until all getPropertyDescriptor() calls in progress have completed.
synchronized(flushingLock) {
while(accessors.intValue() > 0) {
try {
Thread.sleep(100);
} catch(final InterruptedException e) {
//No show stopper
}
}
for(final Iterator<Class<?>> it =
classPropertyDirectory.classMap.keySet().iterator(); it.hasNext();) {
final Class<?> clazz = it.next();
if(clazz.getClassLoader().equals(cl)) {
it.remove();
Introspector.flushFromCaches(clazz);
}
}
}
}
//The rest of the inner classes are omitted...
}
I believe this should work. Suppose thread 1 calls the get... method and thread 2 calls the flush... method at the same time. If thread 1 gets the lock on flushingLock
first, thread 2 will wait for the accessor count to return to 0. In the meantime, new calls to get... can't continue since thread 2 will now have the flushingLock
. If thread 2 got the lock first, it will wait for the accessors to go down to 0 while calls to get... will wait until the flush is complete.
Can anyone see problems with this approach? Are there some scenarios I'm overlooking? Or perhaps I overcomplicate things. Most of all, some java.util.concurrent classes might provide exactly what I'm doing here or there's a standard pattern to apply to this problem I'm nt aware of.
Sorry for the length of this post. It's not that complex but still far from simple matter, so I figure some discussion regarding the right approach would be interesting.
Thanks to everyone who reads this and in advance for any answers.
As far as I understand you can use a ReadWriteLock
here:
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock readLock = lock.readLock();
private Lock writeLock = lock.writeLock();
public PropertyDescriptor getPropertyDescriptor(final Class<?> clazz, final String propertyName) throws Exception {
readLock.lock();
try {
...
} finally {
readLock.unlock();
}
}
public void flushDirectory(final ClassLoader cl) {
writeLock.lock();
try {
...
} finally {
writeLock.unlock();
}
}
Also synchronizing on Class
instance looks suspicious for me - it can interfere with some other synchronization. Perhaps it would be better to use a thread-safe Map
of Future<PropertyDescriptor>
(see, for example, Synchronization in a HashMap cache).
精彩评论