如何在多线程中同步getter和setter

3
public class IntermediateMessage {

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock read = readWriteLock.readLock();
    private final Lock write = readWriteLock.writeLock();

    private volatile double ratio;

    public IntermediateMessage(){
        this.ratio=1.0d;
    }

    public IntermediateMessage(double ratio){
        this.ratio = ratio;
    }

    public double getRatio(){
        read.lock();
        try{
            return this.ratio;
        }
        finally{
            read.unlock();
        }
    }

    public void setRatio(double ratio){ 
        write.lock();
        try{
            this.ratio = ratio;
        }
        finally{
            write.unlock();
        }
    }
}

我有这个对象。在我的应用程序中,我有一个该对象的实例,其中一个线程正在写入ratio变量,而其他线程正在读取ratio变量。这种保护ratio变量的方式正确吗?我需要将ratio声明为volatile吗?


抱歉,我的错。我更新了我的问题。我只是想确保在使用volatile声明时是否需要使用读/写锁。 - codereviewanskquestions
5个回答

1
你在同步方面做了过多的工作,这会导致一些低效率问题。
Java关键字 "volatile" 意味着变量不会被缓存,并且它将为多个线程提供同步访问。
所以你正在锁定一个默认已经同步的变量。
因此,你应该删除 volatile 关键字或删除可重入锁。也许应该删除 volatile,因为当前的同步方式可以更有效地进行多次读取。

1

对于读写原始值,仅使用volatile即可。


1

您是否需要锁定?根据您所描述的有限要求,很可能不需要。但是请阅读以下内容以确保...

  • 您只有一个线程在写入。
    • 这意味着由于竞争写入者“覆盖”彼此(不存在可能的竞争条件),变量值永远不会过时。因此,在考虑单个变量时,不需要锁定以确保完整性。
  • 您没有提到是否需要对多个变量进行原子一致修改。我假设不需要。
    • 如果必须始终使ratio与其他变量一致(例如,在其他对象中),即一组变量必须作为一组同步更改而不是读取部分更改,则需要锁定以为变量集提供原子一致性。然后,必须在单个锁定区域内同时修改一致的变量,并且读取器在读取这些变量集的任何内容之前必须获取相同的锁定(必要时等待处于阻止状态)。
    • 如果可以随时更改比率作为孤立变量并且不需要与其他变量保持一致,则不需要锁定以为变量集提供原子一致性。

你需要使用volatile修饰符吗?是的!

  • 你有多个线程在读取。
  • 变量可以在任何时刻改变,包括在即将被读取的瞬间。
  • volatile修饰符用于多线程应用程序,以确保由“读者”读取的值始终与“写者”写入的值匹配。

此外,对于 long/double 类型,需要使用 volatile 关键字,否则不能保证其原子写入。 - jtahlborn
一般来说,这是正确的,但在这里不是问题。正如所述,有一个单独的写线程。因此,竞争非原子写入(也称为“clobbering”)不是问题。 - Glen Best
问题不在写入,而是在读取(也就是说读取器可能会看到部分更新的值)。因此,在这里它是相关的。 - jtahlborn
以下是編程相關內容的翻譯:感謝 : )。線程需要volatile關鍵字來讓所有線程看到同樣的值。但是long和double類型不存在“部分更新”。字節碼始終以原子方式(跨越所有位)更新,不會設置部分位。volatile確保將新值直接存儲在對所有線程可見的位置,而不是存儲在一個線程或進程的臨時快速寄存器/緩存中。對volatile的讀/寫也將類的其他變量移動到線程可見存儲器中。請參閱JLS: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4和《Java Concurrency In Practice》。希望能幫到您 :) - Glen Best
谢谢,我不知道这个!所以在多线程可以读取不同/不一致值的情况下,也可以进行部分更新,它们也可以读取64位(2 x 不一致的32位)。可能没有广泛讨论,因为那里总是需要volatile。 - Glen Best
显示剩余2条评论

0

如果两个线程试图在同一对象上进行读写,并且您希望数据完整性得到维护。只需使您的getter和setter同步即可。当方法同步时,只有一个线程能够调用同步方法。当一个线程执行其中一个同步方法时,没有其他线程能够调用任何同步方法。因此,在您的情况下,如果您的get和set方法同步,您可以确信如果一个线程正在读取/写入,则没有其他线程可以进行读取/写入。

希望这可以帮助您!


-1

ratio声明为final,这样它就是线程安全的。


那样做不行。Final 意味着在当前类的子类中不能更改定义。Ratio 是 int 类型,而不是设计为不可变的某个类。Static final 可以使其线程安全,但 Q 明确说明 ratio 应该是可变的。 - Glen Best
所有的最终字段都保证是线程安全的。这适用于静态和非静态字段。这与子类无关。 - Steve Kuo
谢谢!请删除“static”的提及 - 那是错误的方向。使用“final”可以使“int”不可变,从而实现线程安全。然后它只能在对象初始化期间设置一次;“static final”只能在类初始化期间设置一次。但是海报想要一个可变对象,所以“final”行不通。 - Glen Best
我正在引导提问者朝着不可变性的方向思考。最好使用不可变的消息对象进行排队(请参见JAL的答案)。 - Steve Kuo

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