Singleton使用枚举类型创建,线程安全问题



美好的一天,我创建了Singleton:

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次执行。我发现,当不允许使用这种修改时,可以通过检测到对象的同时修改的方法来抛出同步的变化。在代码中,我有一个可以防止这种情况的锁,还有相同的锁来更改和检索同步块的列表。我什至不想象为什么会发生这种情况。

CME与并发性无关,而不是名字使您想到的。CME最常见的发生是在单个螺纹上下文中。但是,在这种情况下,线程也涉及。

您的问题来自您正在修改letterListtilesToSend.add(FIRST_INSTANCE.letterList.remove(0));,但是println同时将其迭代。同步在这里无济于事,因为您必须同步比实际可能的块大得多。

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

return new LinkedList<>(FIRST_INSTANCE.letterList);

这样的方式可以通过remove()修改原始列表,而println则迭代副本。

最新更新