2011-04-05 55 views
1

問候,同胞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.LockAtomicInteger的組合來保持正在進行的調用次數的計數,但注意到只能獲得鎖定,而不檢查它是否當前正在使用而不鎖定。現在我在原子整數上使用簡單同步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類可能提供了我正在做的事情,或者有一個標準模式適用於我不知道的這個問題。

對不起,這篇文章的長度。這不是那麼複雜,但仍然遠離簡單的事情,所以我想一些關於正確方法的討論會很有趣。

感謝大家誰閱讀此事先提出任何答案。

+0

你能簡化問題嗎?我的意思是,刪除上下文,並保持你的問題的本質。 (可能有一些基本的例子)。 – weekens 2011-04-05 11:03:08

+0

嘿,我很難保持簡潔。有些東西確實可能被拋出。但是在寫作時,我會記住,這不僅僅針對我自己,也針對其他可能有類似問題並可以使用上下文的人。不過,我會記住未來的問題。 – 2011-04-05 11:28:09

回答

2

據我瞭解,你可以使用這裏ReadWriteLock

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(); 
    } 
} 

同樣在同步Class實例看起來很可疑,我 - 它可以與其他一些干擾同步。也許使用Future<PropertyDescriptor>的線程安全的Map會更好(參見,例如,Synchronization in a HashMap cache)。

+0

ReadWriteLock原理看起來很有趣,我還沒有閱讀它的文檔。只有在沒有寫鎖的情況下才能獲得讀鎖,這似乎是朝着正確方向邁出的一步。唯一的問題是我不知道在使用讀鎖時是否仍然可以獲得寫鎖。或者我錯了? 另外,'Class'實例上的同步對我來說也不舒服。我會檢查同步的集合。 – 2011-04-05 11:35:09

+0

寫入鎖定可以降級到讀取鎖定,但讀取鎖定不能升級到寫入鎖定。 – 2011-04-06 14:12:11

相關問題