首页 > 解决方案 > 来自多个线程的不安全访问以将单个值设置为单个变量

问题描述

我理解从多个线程读取和写入数据需要有一个良好的锁定机制来避免数据竞争。但是,一种情况是:如果多个线程尝试使用单个值写入单个变量,这可能是一个问题。

例如,这里是我的示例代码:

public class Main {
  public static void main(String[] args) {
    final int[] a = {1};
    while(true) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          a[0] = 1;
          assert a[0] == 1;
        }
      }).start();
    }
  }
}

我已经运行这个程序很长时间了,看起来一切都很好。如果此代码会导致问题,我该如何重现?

标签: javathread-safety

解决方案


您的测试用例未涵盖实际问题。您在同一个线程中测试变量的值 - 但该线程已经复制了变量的初始状态,并且当它在线程内更改时,更改对该线程可见,就像在任何单线程应用程序中一样。写操作的真正问题是如何以及何时在其他线程中使用更新的值。

例如,如果您要编写一个计数器,其中每个线程都会增加数字的值,那么您会遇到问题。另一个问题是您的测试操作比创建线程花费的时间更少,因此执行几乎是线性的。如果线程中有更长的代码,则多个线程可以同时访问该变量。我使用 Thread.sleep() 编写了这个测试,已知它是不可靠的(这是我们需要的):

int[] a = new int[]{0};
for(int i = 0; i < 100; i++) {
    final int k = i;
    new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        a[0]++;
        System.out.println(a[0]);
        }
    }).start();
}

如果执行此代码,您将看到输出的不可靠程度。数字的顺序发生了变化(它们不是升序),也有重复和缺失的数字。这是因为该变量被多次复制到 CPU 内存中(每个线程一次),并在操作完成后粘贴回共享 ram。(这不会在完成后立即发生,以节省时间以防以后需要)。

JVM 中可能还有一些其他机制可以为线程复制 RAM 中的值,但我不知道它们。

问题是,即使锁定也不能阻止这些问题。它防止线程同时访问变量,但它通常不确保在下一个线程访问变量之前更新变量的值。


推荐阅读