【问题标题】:Should getters/setters of primitive types be locked with ReadWriteLock in a multithreading application?原始类型的 getter/setter 是否应该在多线程应用程序中使用 ReadWriteLock 锁定?
【发布时间】:2011-02-16 08:45:02
【问题描述】:

我有一个用于多线程应用程序的 Java 类。并发访问的可能性很大。多个并发读取操作不应阻塞,因此我使用的是 ReadWrite 锁。

class Example {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int i;
    private boolean b;

    public int getI() {
      lock.readLock().lock();
      final int tmp = i;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setI( final int i ) {
      lock.writeLock().lock();
      this.i = i;
      lock.writeLock().unlock();
    }

    public boolean getB() {
      lock.readLock().lock();
      final boolean tmp = b;
      lock.readLock().unlock(),
      return tmp;
    }

    public void setB( final boolean b ) {
      lock.writeLock().lock();
      this.b = b;
      lock.writeLock().unlock();
    }
}

为简单起见,我在此示例中省略了锁周围的 try...finally 块。

我想知道是否有必要(或者说推荐)锁定/同步原始类型的 getter 和 setter?我知道 Java 中的分配和返回操作是原子的。但是,通过使用这些锁,我不能确保每个访问者都获得最新的值(等于使用volatile)吗?

如果原语是doublelong 会怎样?

【问题讨论】:

    标签: java multithreading synchronization locking readwritelock


    【解决方案1】:

    这取决于。

    但是请注意,通常您需要在更粗粒度的级别同步操作,例如:

    Example e = ...;
    
    synchronized (e) {
        e.setI(e.getI() + 10);
    }
    

    对于这种情况,您的内部锁是多余的。因此,在使用这些对象的地方应用外部同步可能会更好,而不是内部同步。

    【讨论】:

      【解决方案2】:

      您在 java 中有类似 AtomicInteger 的东西,它适用于多线程应用程序。

      【讨论】:

        【解决方案3】:

        问问自己这个类是否可以实现为不可变类。它将更容易使用并且本质上是线程安全的。您不必担心并发、锁、同步等问题。

        不可变类的示例:

        final class Example {
            private final int i;
            private final boolean b;
        
            public Example(int i, boolean b){
                this.i = i ;
                this.b = b;
            }
        
            public int getI() {
                return i;
            }
        
            public boolean getB() {
                return b;
            }
        }
        

        【讨论】:

        • 不幸的是,这里不可能使用不可变类,因为需要在运行时更改某些属性。
        • @Sven 您可以使用这些新属性创建类的新实例,而不是更改现有实例中的属性。
        • 属性经常改变。在内存和 CPU 的情况下,创建一个新实例不是比更改一个属性更昂贵吗?
        • @Sven 你的对象看起来很简单。同步会更昂贵。 Java 垃圾收集器喜欢小的、短命的对象。请记住,String、Integer 等都是不可变的类。
        • 为简单起见,示例类已被删除。实际的类有 12 个属性,其中 6 个是基元。
        【解决方案4】:

        我会设计你的应用程序,这样你就不会同时访问这样的原始数据类型。添加如此低级别的锁定可能会大大降低您的应用程序的速度,因此不值得对您的应用程序进行多线程处理。

        例如假设您有一个 32 核系统,它可以完美扩展并且运行速度比 1 核快 32 倍。然而,没有锁定的字段访问需要 1 ns,锁定需要 1 us (1000 ns),因此最终您的应用程序可能会慢约 30 倍。 (慢 1000 / 快 32)如果你只有 4 个核心,它可能会慢数百倍,首先破坏了拥有多线程的目的。恕我直言。

        【讨论】:

          【解决方案5】:

          没有必要锁定/同步原始类型的 getter 和 setter - 在大多数情况下将它们标记为 volatile 就足够了(除了你提到的 double 和 long 之外)

          正如之前的一篇文章所提到的,您需要了解读取和更新序列,例如 incrementI(int num) 可能会调用 getI() 和 setI() - 在这种情况下,您可以添加一个 'synchronized incrementI(int num)' 方法添加到您的 Example 类。然后在更高级别上完成锁定,从而减少对单独的读取和写入锁的需求,并且由于数据和行为保持在一起,因此对 OO 友好。如果您同时读取/更新多个字段,此技术将更加有用。

          虽然如果您只是一次读取/写入/更新一个字段,那么 AtomicXX 类更合适

          【讨论】:

            【解决方案6】:

            您不应该对原始类型、String(它们是不可变的)和线程安全类型(如“并发”包中的集合)使用锁。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-10-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-01-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多