java - java getter 是线程安全的吗?

标签 java multithreading

可以同步所有改变对象状态的方法,但不同步任何原子的方法吗?在这种情况下,只返回一个字段?

考虑:

public class A
{
   private int a = 5;

   private static final Object lock = new Object();

   public void incrementA()
   {
       synchronized(lock)
       {
           a += 1;
       }
   }

   public int getA()
   {
       return a;
   }
}

我听到有人争论说 getA()incrementA() 可能会在大致相同的时间被调用,并且有 getA() 返回错误的事情。然而,似乎在同时调用它们的情况下,即使 getter 是同步的,你也可能得到错误的结果。事实上,如果同时调用这些,“正确的事情”似乎甚至没有定义。对我来说最重要的是状态保持一致。

我还听说过有关 JIT 优化的讨论。给定上述类的实例和以下代码(该代码将取决于要在另一个线程中设置的 a):

while(myA.getA() < 10)
{
    //incrementA is not called here
}

将其更改为显然是合法的 JIT 优化:

int temp = myA.getA();
while(temp < 10)
{
    //incrementA is not called here
}

这显然会导致无限循环。 为什么这是合法的优化?如果 a 是 volatile 的,这会是非法的吗?

更新

我对此做了一些测试。

public class Test
{
   private int a = 5;

   private static final Object lock = new Object();

   public void incrementA()
   {
       synchronized(lock)
       {
           a += 1;
       }
   }

   public int getA()
   {
       return a;
   }

   public static void main(String[] args)
   {
       final Test myA = new Test();
       Thread t = new Thread(new Runnable(){
        public void run() {
            while(true)
            {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                myA.incrementA();
            }
        }});
       t.start();
       while(myA.getA() < 15)
       {
           System.out.println(myA.getA());
       }
   }
}

使用几个不同的 sleep 时间,即使 a 不是 volatile 的,这也能起作用。当然,这还不是决定性的,但它仍然可能是合法的。有没有人有一些可能触发此类 JIT 行为的示例?

最佳答案

Is is okay to synchronize all methods which mutate the state of an object, but not synchronize anything which is atomic? In this case, just returning a field?

视具体情况而定。重要的是要认识到同步有两件重要的事情。它不仅仅与原子性有关,而且由于内存同步也是必需的。如果一个线程更新 a 字段,则由于本地处理器上的内存缓存,其他线程可能看不到更新。将 int a 字段设置为 volatile 可以解决这个问题。让 get 和 set 方法同步也可以,但成本更高。

如果您希望能够从多个线程更改和读取 a,最好的机制是使用 AtomicInteger

private AtomicInteger a = new AtomicInteger(5);
public void setA(int a) {
   // no need to synchronize because of the magic of the `AtomicInteger` code
   this.a.set(a);
}
public int getA() {
    // AtomicInteger also takes care of the memory synchronization
    return a.get();
}

I've heard people argue that it's possible for getA() and setA() to be called at roughly the same time and have getA() return to wrong thing.

这是事实,但如果在 setA() 之后调用 getA(),您可能会得到错误的值。错误的缓存值可能会永远存在。

which can obviously result in an infinite loop. Why is this a legal optimization?

这是一种合法的优化,因为使用自己的内存缓存异步运行的线程是您看到它们性能提高的重要原因之一。如果所有内存访问都与主内存同步,则每个 CPU 的内存缓存将不会被使用,线程程序的运行速度会慢很多。

Would this be illegal if a was volatile?

如果有某种方式可以改变a——可能被另一个线程改变,这是不合法的。如果 a 是最终的,那么 JIT 就可以进行优化。如果a volatile 或者get方法被标记为synchronized那么它肯定不是一个合法的优化。

关于java - java getter 是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22841368/

相关文章:

java - 将 Maven Apache 添加到系统 PATH 很困难

c# - 后台工作人员报告忙碌,尽管已被取消且不再存在

java - 仅本地图值不存在时才创建并放置一个 map 值,并获取它 : thread-safe implementation

ios - NSURLConnection 在另一个线程中启动。未调用委托(delegate)方法

clock_gettime(CLOCK_MONOTONIC) 跨核心/线程的单调性

c# - ContinueWith() 直到主机进程结束才执行

java - OpenGL使用错误?

java - 如何自动选择所选项目上方的所有项目

java - 使用反射测试内部私有(private)类的方法

java - Apache Log4j 安全漏洞 - 2.17.0 jar 无法将查找值加载到 log4j2.xml 中