前提条件(通用描述):
1。静态类字段
static List<String> ids = new ArrayList<>();
2。 CompletableFuture#runAsync(Runnable runnable,Executor executor)
在内部召唤
static void main(String args[])
方法
someCollection
来自runAsync
内的3。元素
代码段(具体说明):
private static List<String> ids = new ArrayList<>();
public static void main(String[] args) throws ExecutionException, InterruptedException {
//...
final List<String> lines = Files.lines(path).collect(Collectors.toList());
for (List<String> lines : CollectionUtils.split(1024, lines)) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
List<User> users = buildUsers();
populate(users);
}, executorService);
futures.add(future);
}
private static void populate(List<User> users){
//...
ids.add(User.getId);
//...
}
}
问题描述:
据我从并发的角度理解, 静态变量不能在线程之间共享,因此数据可能会以某种方式丢失。
是否应更改为volatile
或使用它是合理的
ConcurrentSkipListSet<String>
?
答案 0 :(得分:3)
基于代码段:
volatile
,因为它在参考级别上工作,而任务不更新集合对象的引用,它们会改变其状态。是否会更新引用,可能已使用volatile
或AtomicReference
。
静态对象可以在线程之间共享,但该对象必须是线程安全的。并发收集将完成轻载到中载的工作。
但现代的方法是使用流而不是使用共享集合:
List<CompletableFuture<List<String>>> futures = lines.stream()
.map(line -> CompletableFuture.supplyAsync(() -> buildUsers().stream()
.map(User::getId)
.collect(Collectors.toList()),
executorService))
.collect(Collectors.toList());
ids.addAll(futures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toList()));
答案 1 :(得分:3)
在您的特定情况下,有一些方法可以保证ID的线程安全:
同步的例子:
private static synchronized void populate(List<User> users){
//...
ids.add(User.getId);
//...
}
private static void populate(List<User> users){
//...
synchronized (ids) {
ids.add(User.getId);
}
//...
}
我认为使用Collections.newSetFromMap(新的ConcurrentHashMap()是最快的,如果你期望很多用户id。否则,你会熟悉ConcurrentSkipListSet。
volatile是一个糟糕的选择。易失性保证了可见性,但不保证原子性。挥发性使用的典型例子是
volatile a = 1
void threadOne() {
if (a == 1) {
// do something
}
}
void threadTwo() {
// do something
a = 2
}
在这种情况下,您只进行一次写/读操作。作为&#34; a&#34;是不稳定的,然后保证每个线程&#34;看&#34; (读)完全正好1或2。 另一个(坏例子):
void threadOne() {
if (a == 1) {
// do something
a++;
}
}
void threadTwo() {
if (a == 1) {
// do something
a = 2
} else if (a == 2) {
a++
}
}
这里我们做增量操作(读和写)并且可能有不同的结果,因为我们没有原子性。这就是为什么有AtomicInteger,AtomicLong等等。在你的情况下,所有线程都会看到写入值ids,但是他们会写出不同的值,如果你看到内部&#34;添加&#34;在ArrayList的方法中,您将看到如下内容:
elementData[size++] = e;
所以没有人保证大小值的原子性,你可以在一个数组单元中写入不同的id。
答案 2 :(得分:2)
就线程安全而言,变量是否静态无关紧要。 重要的是
从可见性角度来看,您的代码示例很好,因为ids
是静态的,并且会在类创建时初始化。但最好将其标记为final
或volatile
,具体取决于是否可以更改ids
引用。但是违反了安全性,因为ArrayList
不能通过设计在多线程环境中保留其不变量。因此,您应该使用专为多线程访问而设计的集合。 This主题应该有助于选择。