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

时间:2013-08-28 21:13:42

标签: java concurrency

这个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() { ... }
}

3 个答案:

答案 0 :(得分:2)

是。甚至忽略了你没有lastupdateTimecachedData易变的事实,

                lastupdateTime = now;
                cachedData = getDataFromDatabase();

出了故障。
如果getDataFromDatabase因异常而失败,您将更新lastupdateTime,因此将继续返回30秒的陈旧数据,如果null失败,可能会返回getDataFromDatabase第一次尝试。

通过将lastUpdateTime初始化为Long.MIN_VALUE,您将不会失去任何东西,并且会在时钟设置为倒退的系统上更可靠地工作。

System.getCurrentTimeMillis可以转到backwards这不太可能影响30s缓存,但对于这个用例,实际上没有理由不使用System.nanoTime()

答案 1 :(得分:1)

这是一个想法 - 基于synchronized是一种陈旧而低效的机制的原则。

这里我使用AtomicReference<Phaser>来表示正在更新缓存。 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();
  }
}

请注意,这是我第一次使用Phaser - 我希望我做对了。

请注意,如果超时超过从数据库获取数据所需的时间,则此算法可能会中断。

答案 2 :(得分:0)

您的Cash课程包含两个共享 * 可变 *州lastupdateTimecachedDatagetData()可以由两个不同的线程同时调用,它使用一个局部变量now,其他线程可以看到(stack中的#{}}

怎么了?

  • 首先if (now - lastupdateTime < 30000) { return cachedData;},其他主题可以在stalelastupdateTime上看到cachedData数据(您正在访问一个没有同步<的可变状态< / strong>保证)

  • now变量使用lastupdateTime形成不变,因此now - lastupdateTime应在Atomic Block

    中执行

我该怎么办?

只需制作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() { ... }
}