2016-03-04 48 views
1
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); 

} 
} 

在这个代码片段中,在主线程中创建了一个列表。在可运行的实例中,将在由ExecutorService管理的其他线程中访问此列表。如果可运行/可调用访问外部类变量,是否存在任何内存可见性问题?

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

回答

2

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

Runnable类在这里会被编译器转换成一个新的“命名为”类,它看起来像:

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引用实际上与main方法中的list引用不同,它只是指向相同的实例,而h作为同一个名字。

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

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

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

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

因此,外部同步可能需要,这取决于你在任何具有对它的引用(这是否是主线程或运行Runnable任何线程)的线程列表做什么。

2

我想你只能部分地理解这​​个问题。所有线程都能够“看到”列表。问题在于他们“看到”什么。除非您确保在线程启动后列表未被修改,或者确保同步访问列表(或任何由多个线程访问的数据结构),那么您将冒着遇到不一致的风险,或者在最坏的情况下内存损坏。

volatile关键字是不够的。您将需要同步访问使用的代码​​块,或者一些其他的同步原语列表(显示器,信号等)

3

你不得不做出这样的变量final是你Runnable可以用副本创建原因它(也是final)。

final对象构造过程中初始化的字段保证能正确发布到所有线程。

所以它会看到list

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

+0

说“有它的一个副本创建”可以采取意味着*列表*被复制(而不仅仅是对它的引用)。你会考虑澄清一点吗? –

+0

是的,我对如何说出它感到痛苦。随意编辑。 http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class?rq=1 – Thilo

+0

我想不出一个简洁的方式来做到这一点,所以我我添加了一个解释它的答案。为什么在1000年会说1字? –