问候,同胞SO用户。单向同步:如何阻止某个特定的方法?
我目前正在编写一个类的实例将作为JavaBean PropertyDescriptors
缓存。您可以调用方法getPropertyDescriptor(Class clazz, String propertyName)
,该方法将返回相应的PropertyDescriptor
。如果以前未检索到该类,则获取该类的实例并找到右侧描述符。然后将该结果存储在类名对中,以便下一次可以立即返回,而无需查找或需要BeanInfo
。
第一个问题是多个同一类的调用会重叠。这是通过在clazz参数上同步简单解决的。因此,同一个类的多个调用是同步的,但对于不同类的调用可以不受阻碍地继续。这似乎是线程安全和活跃之间的一种体面妥协。
现在,有可能在某些时候某些已被内省的类可能需要卸载。我不能简单地保留对它们的引用,因为这可能导致类加载器泄漏。此外,Introspector
级JavaBeans的API中提到的类加载器的破坏应与内省的冲洗组合:http://download.oracle.com/javase/6/docs/api/java/beans/Introspector.html
所以,我添加了一个方法flushDirectory(ClassLoader cl)
会从缓存中删除任何类,并将其从冲洗内省(与Introspector.flushFromCaches(Class clz)
)提供它已加载该类加载器。
现在我有一个关于同步的新问题。在刷新过程中,不应该将新的映射添加到缓存中,而如果访问仍在继续,则不应启动刷新。换句话说,基本的问题是:
如何确保一段代码可以由多个线程运行,而另一段代码只能由一个线程运行并禁止其他段运行?这是一种单向同步。
首先,我尝试了java.util.concurrent.Lock
和AtomicInteger
的组合来保持正在进行的调用次数的计数,但注意到只能获得锁定,而不检查它是否当前正在使用而不锁定。现在我在原子整数上使用简单同步Object
。这里是我的班级的一个削减版本:
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.addMapping(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...
}
我相信这应该工作。假设线程1调用get ...方法,线程2同时调用flush ...方法。如果线程1首先获得flushingLock
上的锁,则线程2将等待访问者计数返回到0.同时,由于线程2现在具有flushingLock
,所以新的调用get ...无法继续。如果线程2首先获得了锁定,那么它将等待存取器下降到0,同时调用get ...将等待直到刷新完成。
任何人都可以看到这种方法的问题吗?我忽略了一些场景吗?或者我可能会过度复杂。最重要的是,一些java.util.concurrent类可能提供了我正在做的事情,或者有一个标准模式适用于我不知道的这个问题。
对不起,这篇文章的长度。这不是那么复杂,但仍然远离简单的事情,所以我想一些关于正确方法的讨论会很有趣。
感谢大家谁阅读此事先提出任何答案。
你能简化问题吗?我的意思是,删除上下文,并保持你的问题的本质。 (可能有一些基本的例子)。 – weekens 2011-04-05 11:03:08
嘿,我很难保持简洁。有些东西确实可能被抛出。但是在写作时,我会记住,这不仅仅针对我自己,也针对其他可能有类似问题并可以使用上下文的人。不过,我会记住未来的问题。 – 2011-04-05 11:28:09