ReadWriteLock等待设置对象

时间:2013-02-04 13:40:22

标签: java concurrency locking

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

如何正确有效地锁定?我的第一种方法是这样的:

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或任何其他对象。

2 个答案:

答案 0 :(得分:2)

我想不出用高级API构建这种行为的简单方法。请参阅下面的使用等待/通知模式的建议。要点:

  • servicevolatile以确保可见性而无需锁定阅读
  • 始终在service中本地复制
  • doSomething,以防止您检查服务不为空的情况,然后调用service.toString()并获取NPE,因为已调用setService(null);在此期间。
  • 每次循环调整等待时间,以确保不会等待超过5秒。
  • 它使用基本同步,但仅当service为null时 - 在service不为null的基本情况下,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());
    }
}

答案 1 :(得分: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);
    }
}