如果runnable / callable访问外部类变量,是否存在任何内存可见性问题?

时间:2016-03-04 07:17:10

标签: java multithreading

public class TestThread {
ExecutorService executorService = Executors.newFixedThreadPool(10);

public static void main(String[] args) {
    TestThread testThread = new TestThread();
    final List<String> list = new ArrayList<>();
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.print(list);
        }
    }
    testThread.executorService.submit(runnable);

}
}

在此代码段中,将在主线程中创建一个列表。在runnable实例中,将访问由ExecutorService管理的其他线程中的此列表。

所以我的问题是在这种情况下是否会出现线程内存可见性问题?据我所知,如果没有使用sychronization / volatile,则线程无法看到(或完全看到)另一个线程中的值。

3 个答案:

答案 0 :(得分:3)

您必须创建该变量final的原因是您的Runnable可以使用它的副本(也是final)创建。

保证在对象构造期间初始化的

final字段可以正确发布到所有线程。

所以它会看到list

但是,ArrayList不是线程安全的,因此您不应该与多个线程共享它(如果您打算修改它)。

答案 1 :(得分:2)

我认为你只是部分理解了这个问题。所有线程都能够“看到”列表。问题在于他们“看到”了什么。除非您确保在线程启动后未修改列表,或确保同步对列表(或多个线程访问的任何数据结构)的访问,否则您可能会遇到不一致的情况或在最坏的情况下记忆腐败。

volatile关键字是不够的。您需要使用synchronized代码块或其他一些同步原语(监视器,信号量等)来同步对列表的访问。

答案 2 :(得分:2)

展示一下如何实现匿名类以及它们如何访问局部变量可能很有用。

这里的Runnable课程将由编译器转换为新的&#34;命名的&#34; class,看起来像:

class TestThread$1 implements Runnable {
  private final List<String> list;

  TestThread$1(List<String> list) {
    this.list = list;
  }

  @Override
  public void run() {
    System.out.println(list);
  }
}

并将实例化它的行转换为:

Runnable runnable = new TestThread$1(list);

因此,匿名类中使用的list引用实际上与list方法中的main引用不同 - 它恰好指向相同的实例,并且具有相同的名称。

匿名类中引用的变量要求为final,以确保这两个变量将永远指向同一个实例,因为您无法重新分配其中任何一个。 (见Why are only final variables accessible in anonymous class?

现在,正如Thilo所指出的,当构造函数完成时,final成员变量保证对所有线程可见。上面的代码显示这里有一个构造函数,它分配一个final变量。因此,list正文中可用的run()引用保证可见并且与所有线程一致。

但是,这并不能保证列表内部变量的可见性和一致性。如Javadoc for ArrayList中所述:

  

请注意,此实施未同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。

因此,可能需要外部同步,具体取决于您对任何引用它的线程中的列表所做的操作(无论是主线程还是运行Runnable的任何线程)。 / p>