2011-04-05 14 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

相关问题