【问题标题】:Java FutureTask - Multithreaded call to get()Java FutureTask - 多线程调用 get()
【发布时间】:2018-06-28 08:55:09
【问题描述】:

我在一个类中有以下两种方法:

private MyDef myDef;
private FutureTask<MyDef> defFutureTask;

public synchronized void periodEviction() {
       myDef = null;
}

    public MyDef loadMyItems() {

    // if it's not ready use a future - it will block until the results are ready
    if (this.myDef == null) { // this will still not be thread safe
        Callable<MyDef> callableDef = ()->{ return this.loadFromDatabase(); };
        FutureTask<MyDef> defTask = new FutureTask<>(callableDef);
        this.defFutureTask = defTask;
        defFutureTask.run();            
    }        

    try {
        // wait until's it's ready
        this.myDef = this.qDefFuture.get();                     
    } catch(InterruptedException e) {
        log.error(this.getClass(), "Interrupted whilst getting future..");
    } catch(ExecutionException e) {
        log.error(this.getClass(), "Error when executing callable future");
    }         
    return this.myDef; 
}

我想做以下事情:

1) 每隔一小时左右使用periodEviction() 执行一次缓存驱逐。

2) 否则,在 db 加载完成时使用缓存的值。

我相信我误解了 Java 的未来,因为我无法回答“当线程 A、B 和 C 同时调用 loadMyItems() 时会发生什么?”

那么这是否意味着没有执行器之类的东西,这个实现仍然不是线程安全的?

【问题讨论】:

  • 会发生三个线程将启动异步查询,每个线程都会返回其结果,最后一个设置值的线程将在下一小时的缓存中。为了使其线程安全,请将loadMyItems 也设为synchronized。

标签: java futuretask


【解决方案1】:

更简单的方法是根本不缓存对象,而只保留Future。

private CompletableFuture<MyDef> defFuture;

public synchronized void periodEviction() {
    // evict by triggering the request anew
    defFuture = CompletableFuture.supplyAsync(this::loadFromDatabase);
}

public synchronized Optional<MyDef> loadMyItems() {
    try {
        return Optional.of(this.defFuture.get());
    } catch(InterruptedException e) {
        log.error(this.getClass(), "Interrupted whilst getting future..");
    } catch(ExecutionException e) {
        log.error(this.getClass(), "Error when executing callable future");
    }         
    return Optional.empty();
}

需要注意的是,这将在每个驱逐期而不是按需触发数据库查询。

【讨论】:

    【解决方案2】:

    一种超级简单的方法是将loadMyItems 声明为synchronized。但是如果该类有其他访问myDef 的方法,您也必须声明那些synchronized。有时这会导致非常粗粒度的锁定和更慢的性能。

    如果您正在寻找最干净/最快的代码,而不是将periodEviction 声明为synchronized,将myDef 声明为AtomicReference:

    private final AtomicReference&lt;MyDef&gt; myDef = new AtomicReference&lt;&gt;();

    那么periodEviction的正文就是:

    synchronized (myDef) {
        myDef.set(null);
    }
    

    而loadMyItems的正文是:

    synchronized (myDef) {
       if (myDef.get() == null) {
            // perform initialization steps, ending with:
            myDef.set(this.qDefFuture.get());
       }
       return myDef.get();
    }
    

    如果多个线程同时调用loadMyItems,myDef 只会被初始化一次,并且它们都将返回相同的对象(除非以某种方式调用periodEviction 在中间偷偷摸摸)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-11
      • 2015-06-05
      • 1970-01-01
      • 2012-11-30
      • 1970-01-01
      • 2011-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多