当前位置: 首页 > 知识库问答 >
问题:

正确使用可变变量和同步块

伊富
2023-03-14

我正试图在java(或一般情况下)中了解线程安全性。我有这个类(我希望符合POJO的定义),它还需要与JPA提供商兼容:

    public class SomeClass {

        private Object timestampLock = new Object();
        // are "volatile"s necessary?
        private volatile java.sql.Timestamp timestamp;
        private volatile String timestampTimeZoneName;

        private volatile BigDecimal someValue;

        public ZonedDateTime getTimestamp() {
            // is synchronisation necessary here? is this the correct usage?
            synchronized (timestampLock) {
                return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
            }
        }

        public void setTimestamp(ZonedDateTime dateTime) {
            // is this the correct usage?
            synchronized (timestampLock) {
                this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
                this.timestampTimeZoneName = dateTime.getZone().getId();
            }
        }

        // is synchronisation required?
        public BigDecimal getSomeValue() {
            return someValue;
        }

        // is synchronisation required?
        public void setSomeValue(BigDecimal val) {
            someValue = val;
        }
    }

如代码中注释行所述,是否有必要将时间戳时间时区名称定义为易失性,并且同步的块是否按其应有的方式使用?或者我应该只使用synchronized块,而不将timestamptimestampTimeZoneName定义为volatile时间戳时间戳时区名称不应与另一个时间戳错误匹配。

这个链接说

对于所有声明为volatile的变量(包括long和double变量),读写都是原子的

我是否应该理解通过setter/getter访问此代码中的某些Value是线程安全的,这要归功于易失性定义?如果是这样,有没有更好的方法(我不知道“更好”在这里可能是什么意思)来实现这一点?

共有3个答案

濮阳默
2023-03-14
    // is synchronisation required?
    public BigDecimal getSomeValue() {
        return someValue;
    }

    // is synchronisation required?
    public void setSomeValue(BigDecimal val) {
        someValue = val;
    }

我想是的,你需要把同步块,因为考虑一个例子,其中一个线程设置值,同时其他线程试图从getter方法中读取,就像在这个例子中,你会看到同步块。因此,如果您将变量放入方法中,那么您必须要求同步块。

裴心水
2023-03-14

这里没有什么新鲜事,这只是@Cruncher已经说过的更明确的版本:

只要程序中的两个或多个字段必须相互一致,就需要进行synchronized。假设您有两个平行列表,并且您的代码依赖于它们的长度相同。这被称为不变量,如中所示,两个列表的长度总是相同的。

如何编写一个方法append(x, y),在不临时打破不变量的情况下向列表中添加一对新值?你不能。方法必须将一项添加到第一个列表,打破不变量,然后将另一项添加到第二个列表,再次修复它。没有别的办法了。

在单线程程序中,临时中断状态没有问题,因为在append(x, y)运行时,没有其他方法可能使用列表。在多线程程序中不再是这样了。在最坏的情况下,append(x, y)可以将x添加到x列表中,然后调度程序可以在那个确切的时刻挂起线程,以允许其他线程运行。CPU可以在append(x, y)完成任务并使列表再次正确之前执行数百万条指令。在这段时间里,其他线程会看到不变量被破坏,结果可能会损坏您的数据或使程序崩溃。

修复方法是在某个对象上对append(x, y)进行同步,并且(这是重要的部分),对于在同一对象上使用列表进行同步的其他方法。因为在给定的时间,给定对象上只能有一个线程被同步,所以任何其他线程都不可能看到处于不一致状态的列表。

因此,如果线程A调用append(x,y),线程B试图“同时”查看列表,那么线程B会看到列表在线程A完成工作之前还是之后的样子吗?这就是所谓的数据竞赛。目前为止,只有我描述的同步,无法知道哪个线程会赢。到目前为止,我们所做的只是保证一个特定的不变量。

如果哪个线程赢得比赛很重要,那么这意味着还有一些更高级别的不变量也需要保护。您还必须添加更多同步来保护该同步。“线程安全”——用两个小词来命名一个既广泛又深刻的主题。

祝你好运,玩得开心!

桓喜
2023-03-14

要确定是否需要同步,请尝试想象一个地方,在那里可以有一个上下文切换,从而中断代码。

在本例中,如果上下文切换发生在我放置注释的位置,那么在getTimestamp()中,您将从每个时间戳类型中读取不同的值。

此外,虽然赋值是原子的,但这个表达式java。sql。时间戳。from(dateTime.toInstant()) 当然不是,所以你可以在日期时间之间切换上下文。toInstant()和从调用。简而言之,你肯定需要同步块。

synchronized (timestampLock) {
    this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
    //CONTEXT SWITCH HERE
    this.timestampTimeZoneName = dateTime.getZone().getId();
}

synchronized (timestampLock) {
    return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
}

就挥发性而言,我很确定它们是必需的。你必须保证每个线程都得到了变量的最新版本。

这是易变的契约。虽然它可能被synchronized块所覆盖,而且这里实际上不需要volatile,但无论如何写都是好的。如果同步块已经完成了volatile的工作,那么VM将不会执行两次保证。这意味着volatile不会花费你更多,它是一个非常好的闪光灯,可以告诉程序员:“我在多个线程中使用”。

如果这里没有同步的块,那么肯定需要使用易失性。如果在一个线程中调用一个集合,则另一个线程没有队列来告诉它可能已在该线程之外更新。因此,它可能会使用旧的缓存值。如果JIT假设单线程,它可以做很多有趣的优化。那些可以简单地破坏你的程序的人。

现在我不完全确定这里是否需要同步。我的猜测是否定的。不过为了安全起见,我还是会加上它。也可以让java担心同步和使用http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html

 类似资料:
  • 我目前正在从事一个项目,该项目需要一个生成简单XML的对象。我对XML很陌生,还在学习c 我试图实现的是一个可以在代码中这样调用的函数: 在这一行之后,字符串应该包含如下内容:

  • 问题内容: 我注意到的和方法中有一个奇怪的构造: 考虑到JIT最有可能将其视为无操作,因此在本地对象上进行同步有什么意义? 问题答案: 在代码获取对象的监视器之后,立即将对对象的引用存储到中,该对象是组成节点的全局可见节点数组,这些节点组成了以下内容: 此时,在同一线程上执行其他修改方法的其他线程在遍历全局数组时可能会遇到此不完整的节点,换句话说,引用已转义。 虽然在构造时就不可能对新创建的对象进

  • 问题内容: Java中的最终变量是什么?例如:如果我在函数中编写final关键字的含义是什么? 另外,什么时候我要使用最终变量(既作为类变量又作为函数变量)? 为什么必须将同步块中的变量声明为final? 问题答案: 基本上,这只是意味着您无法更改该值。对于实例变量,您必须在构造函数中(或使用变量初始值设定项)分配所有最终变量一次(并且仅分配一次)。同步是一个非常正交的概念。 将 局部 变量设为f

  • #include <stdio.h> int main(void) { int i = 0; char a[100]; for (i = 0; i < sizeof(a); i++) { a[i] = i; } return 0; } 技巧 "x"命令会把最后检查

  • 问题内容: 我如何在for循环中创建变量变量? 这是循环: 在此循环中,我想为每次传递创建一个$ seat变量,但必须像这样递增。第一次通过应该是,下次通过:等等。 所以最后应该是: 等等。 因此$ _POST的变量和内容应该是动态的。 问题答案: 首先,除非缺少某些内容,否则我将为此使用数组。具有像变量,等趋于具有少得多的效用和是更为繁琐比使用的阵列。 话虽这么说,使用以下语法: 最后,PHP具

  • 问题内容: 我有以下查询,该查询使用日期变量,该变量在存储过程内部生成: 问题是@sp_Date值似乎被忽略了,我想知道为什么吗?我定义或使用不正确吗? 问题答案: 您的语法很好,它将返回过去6个月内的行; 您确定类型吗?