简体   繁体   English

这个Java缓存线程是否安全且最优?

[英]Is this Java cache threadsafe and optimal?

Can this Java cache be safer or faster? 这个Java缓存可以更安全或更快吗?

public class Cache {
    private long lastupdateTime = 0L;
    private String cachedData = null;
    public String getData() {
        long now = System.currentTimeMillis();
        if (now - lastupdateTime < 30000) {
            return cachedData;
        }
        else {
            synchronized (this) {
                if (now - lastupdateTime < 30000) {
                    return cachedData;
                }
                else {
                    lastupdateTime = now;
                    cachedData = getDataFromDatabase();
                    return cachedData;
                }
            }
        }
    }
    private String getDataFromDatabase() { ... }
}

Yes. 是。 Even ignoring the fact that you haven't made lastupdateTime or cachedData volatile, 甚至忽略了你没有使lastupdateTimecachedData volatile的事实,

                lastupdateTime = now;
                cachedData = getDataFromDatabase();

is out of order. 出了故障。
If getDataFromDatabase fails with an exception, you will have already updated lastupdateTime , so will keep returning stale data for 30s, possibly returning null if getDataFromDatabase fails on the first attempt. 如果getDataFromDatabase因异常而失败,您将更新lastupdateTime ,因此将继续返回过时数据30秒,如果getDataFromDatabase在第一次尝试失败时可能返回null

You would lose nothing by initializing lastUpdateTime to Long.MIN_VALUE , and would work more reliably on systems whose clocks have been set backwards. 通过将lastUpdateTime初始化为Long.MIN_VALUE ,您将不会失去任何东西,并且可以在时钟向后设置的系统上更可靠地工作。

System.getCurrentTimeMillis can go backwards which is unlikely to affect a 30s cache, but for this use case there's really no reason not to use System.nanoTime() instead. System.getCurrentTimeMillis可以向后移动 ,这不太可能影响30s缓存,但对于这个用例,实际上没有理由不使用System.nanoTime()

Here's an idea - on the principle that synchronized is an archaic and inefficient mechanism. 这是一个想法 - 基于synchronized是一种陈旧而低效的机制的原则。

Here I use an AtomicReference<Phaser> to indicate the cache is being updated. 在这里,我使用AtomicReference<Phaser>来指示正在更新缓存。 The Phaser can be used to await completion of the update. Phaser可用于等待更新完成。

public class Test {

  public static class Cache {
    // Cache timeout.
    private static final long Timeout = 10000;
    // Last time we updated.
    private volatile long lastupdateTime = 0L;
    // The cached data.
    private volatile String cachedData = null;
    // Cache is in the progress of updating.
    private AtomicReference<Phaser> updating = new AtomicReference<>();
    // The next Phaser to use - avoids unnecessary Phaser creation.
    private Phaser nextPhaser = new Phaser(1);

    public String getData() {
      // Do this just once.
      long now = System.currentTimeMillis();
      // Watch for expiry.
      if (now - lastupdateTime > Timeout) {
        // Make sure only one thread updates.
        if (updating.compareAndSet(null, nextPhaser)) {
          // We are the unique thread that gets to do the updating.
          System.out.println(Thread.currentThread().getName() + " - Get ...");
          // Get my new cache data.
          cachedData = getDataFromDatabase();
          lastupdateTime = now;
          // Make the Phaser to use next time - avoids unnecessary creations.
          nextPhaser = new Phaser(1);
          // Get the queue and clear it - there cannot be any new joiners after this.
          Phaser queue = updating.getAndSet(null);
          // Inform everyone who is waiting that they can go.
          queue.arriveAndDeregister();
        } else {
          // Wait in the queue.
          Phaser queue = updating.get();
          if (queue != null) {
            // Wait for it.
            queue.register();
            System.out.println(Thread.currentThread().getName() + " - Waiting ...");
            queue.arriveAndAwaitAdvance();
            System.out.println(Thread.currentThread().getName() + " - Back");
          }
        }
      }
      // Let them have the correct data.
      return cachedData;
    }

    private String getDataFromDatabase() {
      try {
        // Deliberately wait for a bit.
        Thread.sleep(5000);
      } catch (InterruptedException ex) {
        // Ignore.
      }
      System.out.println(Thread.currentThread().getName() + " - Hello");
      return "Hello";
    }
  }

  public void test() throws InterruptedException {
    System.out.println("Hello");
    // Start time.
    final long start = System.currentTimeMillis();
    // Make a Cache.
    final Cache c = new Cache();
    // Make some threads.
    for (int i = 0; i < 20; i++) {
      Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
          while (System.currentTimeMillis() - start < 60000) {
            c.getData();
            try {
              Thread.sleep(500);
            } catch (InterruptedException ex) {
              // Ignore.
            }
          }
        }
      });
      t.setName("Thread - " + i);
      t.start();
      // Stagger the threads.
      Thread.sleep(300);
    }
  }

  public static void main(String args[]) throws InterruptedException {
    new Test().test();
  }
}

Please note that this is the first time I have used a Phaser - I hope I have it right. 请注意,这是我第一次使用Phaser - 我希望我做对了。

Note that this algorithm may break down if the timeout is longer than the time it takes to get the data from the database. 请注意,如果超时超过从数据库获取数据所需的时间,则此算法可能会中断。

You have your Cash class with two shared * mutable * state lastupdateTime and cachedData , getData() can be invoked conurrenctly by two different threads, it use a local variable now that can be visible to other threads (alocated in the stack ) 你的Cash类有两个共享 * mutable *状态lastupdateTimecachedDatagetData()可以由两个不同的线程同时调用,它now使用一个局部变量,对其他线程可见(在stack备份)

What's wrong ? 怎么了 ?

  • First if (now - lastupdateTime < 30000) { return cachedData;} , Other thread can see stale data on lastupdateTime and cachedData (you are accessing to a thared mutable state without synchronization guarantee) 首先是if (now - lastupdateTime < 30000) { return cachedData;} ,其他线程可以看到lastupdateTimecachedData上的stale数据(你正在访问一个没有同步保证的可变状态)

  • now variable form such a invariant with lastupdateTime , so now - lastupdateTime should be executed in an Atomic Block now变量与lastupdateTime形成这样的不变量 ,所以now - lastupdateTime应该在Atomic Block执行

What can I do ? 我能做什么 ?

Just make method synchronized , and make your class totally thread-safe 只需使method synchronized ,并使您的类完全是线程安全的

public class Cache {
    private long lastupdateTime = 0L;
    private String cachedData = null;
    public synchronized String getData() {
           long now = System.currentTimeMillis()
           if (nano - lastupdateTime < 30000) {
                return cachedData;
           }
           else {
               lastupdateTime = now;
               cachedData = getDataFromDatabase();
               return cachedData;
           }                
    }
    private String getDataFromDatabase() { ... }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM