![](/img/trans.png)
[英]Is it advisable to always use volatile variables with synchronized blocks/methods?
[英]Proper use of volatile variables and synchronized blocks
我试图在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;
}
}
如代码中注释的行中所述,是否有必要将timestamp
和timestampTimeZoneName
定义为volatile
并且是应用的synchronized
块? 或者我应该只使用synchronized
块而不是将timestamp
和timestampTimeZoneName
定义为volatile
? timestampTimeZoneName
的timestamp
不应错误地与另一个timestamp
匹配。
这个链接说
对于声明为volatile的所有变量,读取和写入都是原子的(包括长变量和双变量)
我是否应该理解通过setter / getter访问此代码中的someValue
是否是线程安全的,这要归功于volatile
定义? 如果是这样,有没有更好的(我不知道“更好”在这里可能意味着什么)实现这一目标的方法?
要确定是否需要同步,请尝试想象一个可以使用上下文切换来破坏代码的地方。
在这种情况下,如果上下文切换发生在我放置注释的位置,那么在getTimestamp()
您将从每个时间戳类型中读取不同的值。
另外,虽然赋值是原子的,但这个表达式是java.sql.Timestamp.from(dateTime.toInstant());
当然不是,所以你可以在dateTime.toInstant()
和from
的调用之间进行上下文切换。 总之,你肯定需要同步块。
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的工作,则VM将不会执行两次保证。 这意味着不再需要挥发性物质,这是一个非常好的闪光灯,对程序员说:“我已经在多个线程中使用了”。
对于someValue
:如果这里没有同步块,那么volatile肯定是必要的。 如果在一个线程中调用一个集合,则另一个线程没有队列告诉它可能已在此线程之外更新。 因此它可能使用旧的缓存值。 如果它采用单线程,JIT可以做很多有趣的优化。 可以简单地破坏你的程序。
现在我不完全确定这里是否需要同步。 我的猜测是否定的。 无论如何我会添加它以保证安全。 或者你可以让java担心同步并使用http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html
这里没什么新东西,这只是@Cruncher已经说过的更明确的版本:
只要程序中的两个或多个字段彼此一致,您就需要synchronized
。 假设您有两个并行列表,并且您的代码依赖于它们两者的长度相同。 这被称为不变量 ,两个列表总是相同的长度。
如何编写一个方法,追加(x,y),为列表添加一对新值而不会暂时破坏不变量? 你不能。 该方法必须将一个项添加到第一个列表,打破不变量,然后将另一个项添加到第二个列表,再次修复它。 别无他法。
在单线程程序中,临时中断状态没有问题,因为当append(x,y)正在运行时,没有其他方法可以使用列表。 在多线程程序中不再是这样。 在最坏的情况下,append(x,y)可以将x添加到x列表,然后调度程序可以在该确切时刻挂起线程以允许其他线程运行。 在附加(x,y)完成作业并使列表再次正确之前,CPU可以执行数百万条指令。 在所有这段时间内,其他线程会看到破坏的不变量,并可能导致数据损坏或导致程序崩溃。
修复是为了使append(x,y)在某个对象上synchronized
,并且(这是重要的部分),对于使用列表在同一对象上synchronized
每个其他方法 。 由于在给定时间只能在给定对象上synchronized
一个线程,因此任何其他线程都不可能看到处于不一致状态的列表。
因此,如果线程A调用append(x,y),并且线程B尝试“同时”查看列表,那么线程B是否会在线程A执行其工作之前或之后看到列表的样子? 这被称为数据竞赛 。 只有我到目前为止所描述的同步,没有办法知道哪个线程会赢。 到目前为止我们所做的就是保证一个特定的不变量。
如果哪个线程赢得比赛很重要,那么这意味着有一些更高级别的不变量也需要保护。 您还必须添加更多同步以保护该同步。 “线程安全” - 用两个小词来命名一个既宽又深的主题。
祝好运并玩得开心点!
// is synchronisation required?
public BigDecimal getSomeValue() {
return someValue;
}
// is synchronisation required?
public void setSomeValue(BigDecimal val) {
someValue = val;
}
我认为是的,您需要放置同步块,因为考虑一个示例,其中一个线程正在设置该值,同时其他线程正在尝试从getter方法读取, 就像在示例中您将看到同步块一样。因此,如果您在方法中使用变量,则必须要求同步块。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.