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,则线程无法看到(或完全看到)另一个线程中的值。
答案 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>