gpt4 book ai didi

java - 使用枚举类型创建的单例,线程安全问题

转载 作者:行者123 更新时间:2023-12-02 12:13:35 26 4
gpt4 key购买 nike

美好的一天,我已经创建了单例:

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

public enum Singleton {
FIRST_INSTANCE;

String[] scrabbleLetters = {
"a","a","a","a","a","a","a","a","a","b","b","b","b","b","b","b","b","b",
"c","c","c","c","c","c","c","c","c","d","d","d","d","d","d","d","d","d","d",
};

private LinkedList<String> letterList = new LinkedList<>(Arrays.asList(scrabbleLetters));

private Object lock = new Object();

private Singleton() {
Collections.shuffle(letterList);
}

public static Singleton getInstance() {
return FIRST_INSTANCE;
}

public LinkedList<String> getLetterList() {
synchronized (lock) {

return FIRST_INSTANCE.letterList;
}
}

public LinkedList<String> getTiles(int howManyTiles) {
synchronized (lock) {

LinkedList<String> tilesToSend = new LinkedList<>();
for(int i=0; i<= howManyTiles; i++) {
tilesToSend.add(FIRST_INSTANCE.letterList.remove(0));
}
return tilesToSend;

}
}

}

我已经用这个例子测试了它的线程安全性:

import java.util.LinkedList;

public class ScrabbleTest {
public static void main(String[] args) {
Runnable getTiles = () -> {

System.out.println("In thread : " +Thread.currentThread().getName());
Singleton newInstance = Singleton.getInstance();
System.out.println("Instance ID: " + System.identityHashCode(newInstance));
System.out.println(newInstance.getLetterList());

LinkedList<String> playerOneTiles = newInstance.getTiles(7);
System.out.println("Player : " + Thread.currentThread().getName() + playerOneTiles);
System.out.println("Got Tiles for " + Thread.currentThread().getName());
};

new Thread(getTiles, "First").start();
new Thread(getTiles, "Second").start();
}
}

执行10次后,我确信没有问题,但是当我上次运行它时,我收到了这个堆栈跟踪:

In thread : Second
In thread : First
Instance ID: 1380197535
Instance ID: 1380197535
[d, d, b, c, b, b, a, d, c, d, a, d, c, a, a, d, c, a, a, b, d, b, b, a, b, c, a, d, c, a, c, b, c, c, b, d, d]
Player : First[d, d, b, c, b, b, a, d]
Got Tiles for First
Exception in thread "Second" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(Unknown Source)
at java.util.LinkedList$ListItr.next(Unknown Source)
at java.util.AbstractCollection.toString(Unknown Source)
at java.lang.String.valueOf(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at ScrabbleTest.lambda$0(ScrabbleTest.java:10)
at java.lang.Thread.run(Unknown Source)

这种异常很少发生退出,20次执行大约1次。我发现,当不允许进行对象的并发修改时,检测到并发修改的方法可能会引发 ConcurrentModificationException。在代码中,我有一个锁可以防止这种情况,也有相同的锁用于更改和检索同步块(synchronized block)的列表。我什至无法想象为什么会发生这种情况。

最佳答案

CME 与并发性的关系并不像它的名字让您想象的那么多。 CME 最常见的情况是在单线程上下文中。然而,在这种情况下,也涉及到线程。

您的问题来自于 tilesToSend.add(FIRST_INSTANCE.letterList.remove(0));,您正在其中修改 letterList,但它正在迭代同时通过println。同步在这里没有帮助,因为您必须同步比实际可能的 block 大得多的 block 。

这里的简单解决方案是在 getLetterList() 中返回列表的副本,例如

return new LinkedList<>(FIRST_INSTANCE.letterList);

这样,当 println 迭代副本时,可以通过 remove() 修改原始列表。

关于java - 使用枚举类型创建的单例,线程安全问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46362632/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com