getIt()
是否线程安全?为什么?public class X {
private long myVar;
public void setIt(long var){
myVar = var;
}
public long getIt() {
return myVar;
}
}
getIt()
是否线程安全?为什么?public class X {
private long myVar;
public void setIt(long var){
myVar = var;
}
public long getIt() {
return myVar;
}
}
该代码不具备线程安全性。在Java中,类型为long
和double
的变量被视为两个分别由32位组成的变量。一个线程可能会在另一个线程读取半个值之前写入并已经写入了半个值。在这种情况下,读者将看到一个本不应该存在的值。
要使其线程安全,您可以将myVar
声明为volatile
(Java 1.5或更高版本)或使setIt
和getIt
都synchronized
。
请注意,即使myVar
是32位的int
,您仍然可能遇到线程问题,其中一个线程可能正在读取另一个线程已更改的过时值。这可能是因为CPU缓存了该值。要解决此问题,您需要再次将myVar
声明为volatile
(Java 1.5或更高版本),或使setIt
和getIt
都synchronized
。
还值得注意的是,如果您在后续的setIt
调用中使用getIt
的结果,例如x.setIt(x.getIt() * 2)
,则可能需要跨两个调用进行synchronize
:
synchronized(x)
{
x.setIt(x.getIt() * 2);
}
如果没有额外的同步,另一个线程可能会在getIt
和setIt
调用之间更改该值,导致另一个线程的值丢失。
long
的原子写入,缺少synchronized
也可能导致一个线程调用了setIt()
,即使此调用已经完成,另一个线程仍然可以调用getIt()
并且此调用可能返回myVar
的旧值。
synchronized
关键字不仅提供了对块或方法的独占访问,还保证第二个线程知道变量的更改。synchronized
,或者将成员myVar
标记为volatile
。原子操作不能交错执行,因此可以在不担心线程干扰的情况下使用它们。但是,这并不能消除所有同步原语的需求,因为仍然可能出现内存一致性错误。使用
volatile
变量可以降低内存一致性错误的风险,因为对volatile
变量的任何写入都会与随后读取相同变量的操作建立happens-before关系。这意味着对volatile
变量的更改始终对其他线程可见。此外,当线程读取volatile
变量时,它不仅可以看到最新的变化,还可以看到导致变化的代码的副作用。
不是的,因为在Java中long类型不是原子性的,所以一个线程可以在setIt方法中写入long类型的32位,然后getIt方法读取该值,接着setIt方法可以设置另外的32位。
因此,最终结果是getIt返回了一个从未有效过的值。
通常情况下,应该是线程安全的,但不能保证线程安全。不同核心在CPU缓存中可能有不同版本的问题,或者对于所有架构来说,存储/检索可能不是原子性的。建议使用AtomicLong
类。
getter 不是线程安全的,因为它没有受到任何保证最新可见性的机制的保护。你的选择有:
myVar
声明为 final(但这样就无法更改它)myVar
声明为 volatilemyVar
由于这是一个只读方法,您应该同步set
方法。
编辑:我明白为什么get方法也需要同步了。Phil Ross解释得很好,干得漂亮。