允许同步 getter/setter 方法进行多次获取

3

一个常见的访问字段的方式是同步getter和setter方法。一个简单的例子,使用int类型:

private int foo = 0;
public synchronized int get(){return this.foo;}
public synchronized void set(int bar){ this.foo = bar;}

现在,虽然这是一种确保访问线程安全的安全方式,但它也揭示了只有一个线程可以同时读取foo
如果许多线程经常读取foo,并且仅有时更新此变量,则会浪费大量资源。getter方法可以被多个线程同时调用而不会出现任何问题。
是否有任何已建立的模式来处理这个问题?或者你会以最优雅的方式解决这个问题?
3个回答

5

请看ReadWriteLock。这个接口是你要寻找的,它允许多个读取者但只有一个写入者。

例如:

private int foo = 0;

private ReadWriteLock rwLock = /* use some implementation of ReadWriteLock here */;

public int get() {
    Lock l = rwLock.readLock();
    int result = 0;
    l.lock();
    try {
        result = this.foo;
    }
    catch(Exception ex) {
        // may throw the Exception here
    }
    finally {
        l.unlock();
    }
    return result;
}

public void set(int bar){ 
    Lock l = rwLock.writeLock();
    l.lock();
    try {
        this.foo = bar;
    }
    catch(Exception ex) {
        // may throw the Exception here
    }
    finally {
        l.unlock();
    }
}

谢谢,这看起来就是我一直在寻找的东西 :). 不过你示例中的同步方法是否有违锁的目的,或者我对synchronized的工作原理有误解? - Chippen
@Chippen 您是正确的。我忘记删除修饰符。我会更新我的答案。 - Viktor Seifert

3
如果您只是设置和获取变量:那么您可以使用volatile并删除synchronized方法。
如果您对整数执行任何操作,例如加法,则应使用AtomicInteger
编辑:
如果存在这样的场景,即该字段被多次读取并更新了几次,则有一种称为IMMUTABLE的模式。这是实现线程安全的一种方式。
class ImmutableClass{
        private final int a;

        public ImmutableClass(int a) {
            this.a  = a;
        }

        public int getA(){
            return a;
        }

        /* 
         * No setter methods making it immutable and Thread safe
         */
    }

如果您想要更加详细了解不可变性,我建议您阅读Java并发编程实战

如果您想要掌握更高级的方法:可以使用读写锁


这只是一个例子,用来说明问题,实际内容可能更加复杂,不适合原子操作。但读取方法本身是线程安全的这一事实仍然成立。 - Chippen
你可以查看Java并发获取更多相关信息。 - Narendra Pathai
我已经编辑了答案。希望这回答了你所寻找的内容 :) - Narendra Pathai
是的,读写锁看起来是解决这个问题的好方法。谢谢。 - Chippen
1
很高兴能帮忙!只想指出一点,一旦你觉得已经得到了满意的答案,你需要将该答案标记为“已接受”。http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work - Narendra Pathai
啊,谢谢。我在stackoverflow上不是经常贡献者 ;) - Chippen

0

对于像您这样简单的示例,其中只有一个字段并且在setter中没有复杂的逻辑需要真正要求互斥,您可以将该字段声明为volatile并删除synchronized关键字。

对于更复杂的内容,您可以使用Cheap Read-Write Lock模式:

public class CheapReadWriteLockPattern {

  // SINGLE-FIELD:
  private volatile Object obj;

  public Object read() {
    return obj;
  }

  public synchronized void write(Object obj) {
    this.obj = obj;
  }

  // MULTI-FIELD:
  private static class Ref {
    final Object x;
    final Object y;
    final Object z;

    public Ref(Object x, Object y, Object z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  }

  private volatile Ref ref = new Ref(null, null, null);

  public Object readX() { return ref.x; }
  public Object readY() { return ref.y; }
  public Object readZ() { return ref.z; }

  public synchronized void writeX(Object x) {
    ref = new Ref(x, ref.y, ref.z);
  }
  public synchronized void writeY(Object y) {
    ref = new Ref(ref.x, y, ref.z);
  }
  public synchronized void writeZ(Object z) {
    ref = new Ref(ref.x, ref.y, z);
  }
}

这个廉价的读写锁模式相比 Viktor 和 Narendra 在答案中提到的库读/写锁有什么优势吗? - Chippen
@Chippen 是的,读者永远不会让作家挨饿。此外,volatile 读写操作比锁定操作更便宜。在读取情况下也不会导致对象分配。 - Chris Vest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接