java 线程同步

2022-09-03 17:16:06

在下面的类中,方法线程是否安全,为什么?getIt()

public class X { 
  private long myVar; 
  public void setIt(long  var){ 
    myVar = var; 
   }  
   public long getIt() { 
     return myVar; 
  } 
}

答案 1

它不是线程安全的。在 Java 中,类型变量和 Java 中的变量被视为两个单独的 32 位变量。一个线程可能正在写入,并且当另一个线程读取两半时,写入了一半的值。在这种情况下,读者将看到一个永远不应该存在的值。longdouble

要使此线程安全,可以声明为(Java 1.5 或更高版本),也可以同时声明为 和 。myVarvolatilesetItgetItsynchronized

请注意,即使是 32 位,您仍然可能遇到线程问题,其中一个线程可能正在读取另一个线程已更改的过期值。发生这种情况的原因可能是 CPU 已缓存该值。要解决此问题,您还需要声明为(Java 1.5 或更高版本)或将 和 同时设置为 。myVarintmyVarvolatilesetItgetItsynchronized

还值得注意的是,如果您在后续调用中使用的结果,例如,您可能希望跨越两个调用:getItsetItx.setIt(x.getIt() * 2)synchronize

synchronized(x)
{
  x.setIt(x.getIt() * 2);
}

如果没有额外的同步,另一个线程可能会更改 和 调用之间的值,从而导致另一个线程的值丢失。getItsetIt


答案 2

这不是线程安全的。即使您的平台保证 原子写入 ,缺少 使得一个线程调用成为可能,即使在此调用完成后,另一个线程也可以调用,并且此调用可能返回 的旧值 。longsynchronizedsetIt()getIt()myVar

该关键字的作用不仅仅是一个线程对块或方法的独占访问。它还保证第二个线程被告知变量的更改。synchronized

因此,您必须将这两种方法都标记为 或将成员标记为 .synchronizedmyVarvolatile

这里有一个关于同步的很好的解释:

原子操作不能交错,因此可以使用它们而不必担心线程干扰。但是,这并不能消除同步原子操作的所有需求,因为内存一致性错误仍然可能。使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写入都会与同一变量的后续读取建立“发生之前”关系。这意味着对易失性变量的更改始终对其他线程可见。更重要的是,这也意味着当线程读取易失性变量时,它不仅会看到易失性变量的最新变化,还会看到导致变化的代码的副作用。