我对Java中的多线程很新。因为我需要一个线程安全的单例(我实现为枚举),我写了一个小的测试代码,它会产生一个奇怪的输出。
守则:
rsync -ar --include="*/" --include="*.txt" --exclude="*" username@hostname:/home/user/dir1(dir2,dirHello) destination_dir
所以每个线程都在属性" message"中写入它的名字。然后将枚举打印到STDOUT。我得到以下输出,我觉得很奇怪:
public enum EnumSingleton {
INSTANCE;
/** state variables */
private String message;
/** Constructor */
private EnumSingleton() {
}
/** add well-known accessor for the instance (is NOT necessary) */
public static EnumSingleton getInstance() {
return INSTANCE;
}
/** Accessors */
public String getMessage() {
return message;
}
public void setMessage(String name) {
this.message = name;
}
}
public class App {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int b = i;
Thread thread = new Thread("Thread #" + b) {
@Override
public void run() {
EnumSingleton singleton = EnumSingleton.getInstance();
singleton.setMessage("Message written by "+this.getName());
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage());
}
};
thread.start();
}
}
}
我期望的是我得到每个循环计数器(0-9)一条消息。但是在这个例子中我有多个由Thread#3写的消息,那怎么可能呢?有竞争条件吗?
如果我的代码是废话:我如何正确测试我的单例以获得线程安全?
答案 0 :(得分:6)
这里有一个明确的竞争条件,因为你在枚举的单例实例中有一个变量message
。你的线程都在同时写入和读取该变量,所以你希望看到这样的结果。
enum构造意味着单例对象的 creation 是线程安全的,但是仍然需要正确处理对其中方法的调用。
执行您要找的工作的方法是让message
成为thread local变量,或者将消息的设置及其读取放在一个synchronized
内阻止,可能锁定单例对象。
答案 1 :(得分:5)
你的单身人士不是线程安全的 - 你没有做任何事情来保证message
变量的可见性。
你可以为此volatile
。但请注意,输出可能有很多不同之处 - 特别是对于每个Message written by Thread #i
,您不一定会得到一个i
。
答案 2 :(得分:4)
发生的事情是:
singleton.setMessage("Message written by #7");
singleton.setMessage("Message written by #2");
singleton.setMessage("Message written by #0");
singleton.setMessage("Message written by #3");
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage())
答案 3 :(得分:2)
你混合了两件不同的东西。一个是对象是否是单例以及对象是否是可变/不可变的。在你的情况下,你有一个单例对象,但它是可变的。
这意味着线程确实获得了相同的对象实例并改变了EnumSingleton
对象的状态。
在您的情况下,您希望使对象不可变或执行以下操作:
private static final Object lock = new Object();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int b = i;
Thread thread = new Thread("Thread #" + b) {
@Override
public void run() {
synchronized (lock) {
EnumSingleton singleton = EnumSingleton.getInstance();
singleton.setMessage("Message written by "+this.getName());
System.out.println("Current thread "+this.getName() + ": "+singleton.getMessage());
}
}
};
thread.start();
}
}
上面锁定对象的重点是使设置和获取消息的操作成为原子操作。只需同步setMessage()
和getMessage()
方法,您就无法逃脱。