2013-02-04 29 views
2

DependingService依赖于service异步和动态注入到DependingService.setService(Object)的对象。如果在service对象被设置之前调用DependingService.doSomething(),则线程应该等待5秒钟以使service可用。ReadWriteLock等待对象被设置

如何做适当和有效的锁定?我的第一种方法是这样的:

import java.util.concurrent.TimeUnit; 
import java.util.concurrent.locks.Condition; 
import java.util.concurrent.locks.ReadWriteLock; 
import java.util.concurrent.locks.ReentrantReadWriteLock; 

public class DependingService { 

    private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); 
    private final Condition condition = rwLock.writeLock().newCondition(); 
    private Object service; 


    // service injected dynamically by container 
    public void setService(final Object service) { 
     rwLock.writeLock().lock(); 
     try { 
      this.service = service; 
      System.out.println("Signalling"); 
      condition.signalAll(); 
     } finally { 
      rwLock.writeLock().unlock(); 
     } 
    } 


    public void doSomething() { 
     rwLock.readLock().lock(); 
     try { 
      if (service == null) { 
       // we can't upgrade to write lock, so release read lock first 
       rwLock.readLock().unlock(); 
       rwLock.writeLock().lock(); 
       try { 
        if (service == null) { 
         System.out.println("Waiting fo 5 seconds"); 
         condition.await(5, TimeUnit.SECONDS); 
        } 
       } catch (final InterruptedException e) { 
        e.printStackTrace(); 
       } finally { 
        // downgrade to read lock 
        rwLock.readLock().lock(); 
        rwLock.writeLock().unlock(); 
       } 
       if (service == null) { 
        throw new RuntimeException("service is null"); 
       } 
      } 

      // use the service 
      System.out.println(service.toString()); 
     } finally { 
      rwLock.readLock().unlock(); 
     } 
    } 

} 

编辑: 注意DependingService.setService(Object)可以被设置为NULL随时随地多次或任何其他对象。

+0

'setServive'多久调用一次?每秒多次(100+)?每x秒/分钟一次? – assylias

+0

你的代码的一个问题是'condition.await(...)'可能会虚假地唤醒 - 所以你应该把这个语句放在一个循环中,在这种情况下你的超时将不再起作用。 – assylias

+0

assylias:通常应该只调用一次,但没有人不知道(它实际上是在OSGi环境中) –

回答

0

也许是更好地使用CountDownLatch是这样的:

public class DependingService { 
    private final CountDownLatch serviceLatch = new CountDownLatch (1); 
    private Object service; 

    public void setService (final Object service) 
    { 
     this.service = service; 
     serviceLatch.countDown(); 
    } 

    public void doSomething() throws InterruptedException 
    { 
     if (!serviceLatch.await (5, TimeUnit.SECONDS)) 
      throw new RuntimeException ("Service is still null"); 

     // Service is not null here 
     System.out.println (service); 
    } 
} 
+0

它没有明确提到,但容器稍后可能会调用setService(null),所以你的方法在这种情况下不起作用。但是,感谢您向我介绍CountDownLatch :-) –

2

我想不出一个简单的方法来构建具有高层次的API行为。请参阅下面的使用等待/通知模式的建议。要点:

  • servicevolatile,以确保在无需读锁
  • service总是在doSomething本地复制,以防止在您检查服务的情况下,能见度不为空,然后打电话service.toString()并获得NPE因为在此期间setService(null);已被调用。
  • 在每个while循环中调整等待时间以确保您不会等待超过5秒钟。
  • 它使用基本同步,但仅当服务为空时 - 在服务不为空的基本情况下,在doSomething中没有争用。如果每ms调用setService多次,则可能会遇到性能问题。

注意:未经测试。

public class DependingService { 

    private final Object lock = new Object(); 
    private volatile Object service; 

    // service injected dynamically by container 
    public void setService(final Object service) { 
     this.service = service; 
     synchronized(lock) { 
      lock.notifyAll(); 
     } 
    } 

    public void doSomething() throws InterruptedException { 
     //make a local copy to avoid problems due to service becoming 
     //null in the middle of the method 
     Object localService = service; 
     if (localService == null) { 
      long end = System.nanoTime() + TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); 
      synchronized(lock) { 
       while ((localService = service) == null) { 
        long waitNanos = end - System.nanoTime(); 
        if (waitNanos < 0) break; 
        lock.wait(waitNanos/1000000); 
       } 
      } 
     } 
     if (localService == null) { 
      throw new RuntimeException("timeout: service is still null"); 
     } 
     // use the service 
     System.out.println(localService.toString()); 
    } 
} 
+0

为什么你要经历所有的麻烦才能使用nanos而不是millis? – jtahlborn

+0

@ jtahlborn的确不需要。在测量短时间段时,我倾向于默认使用nanotime - 但是持续5秒,currentMillis也可以工作。 – assylias