問候,同胞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