java 线程同步
在下面的类中,方法线程是否安全,为什么?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 中,类型变量和 Java 中的变量被视为两个单独的 32 位变量。一个线程可能正在写入,并且当另一个线程读取两半时,写入了一半的值。在这种情况下,读者将看到一个永远不应该存在的值。long
double
要使此线程安全,可以声明为(Java 1.5 或更高版本),也可以同时声明为 和 。myVar
volatile
setIt
getIt
synchronized
请注意,即使是 32 位,您仍然可能遇到线程问题,其中一个线程可能正在读取另一个线程已更改的过期值。发生这种情况的原因可能是 CPU 已缓存该值。要解决此问题,您还需要声明为(Java 1.5 或更高版本)或将 和 同时设置为 。myVar
int
myVar
volatile
setIt
getIt
synchronized
还值得注意的是,如果您在后续调用中使用的结果,例如,您可能希望跨越两个调用:getIt
setIt
x.setIt(x.getIt() * 2)
synchronize
synchronized(x)
{
x.setIt(x.getIt() * 2);
}
如果没有额外的同步,另一个线程可能会更改 和 调用之间的值,从而导致另一个线程的值丢失。getIt
setIt
这不是线程安全的。即使您的平台保证 原子写入 ,缺少 使得一个线程调用成为可能,即使在此调用完成后,另一个线程也可以调用,并且此调用可能返回 的旧值 。long
synchronized
setIt()
getIt()
myVar
该关键字的作用不仅仅是一个线程对块或方法的独占访问。它还保证第二个线程被告知变量的更改。synchronized
因此,您必须将这两种方法都标记为 或将成员标记为 .synchronized
myVar
volatile
这里有一个关于同步的很好的解释:
原子操作不能交错,因此可以使用它们而不必担心线程干扰。但是,这并不能消除同步原子操作的所有需求,因为内存一致性错误仍然可能。使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写入都会与同一变量的后续读取建立“发生之前”关系。这意味着对易失性变量的更改始终对其他线程可见。更重要的是,这也意味着当线程读取易失性变量时,它不仅会看到易失性变量的最新变化,还会看到导致变化的代码的副作用。