下面的代码有问题。从同一进程的多个线程调用同一个单例类的多个实例。我希望程序只打印一次“正在创建实例”,但它打印3次(即每个线程)。
package SingleTon;
class SingleTon implements Runnable {
String name;
int salary;
private static SingleTon singleTonInstance;
SingleTon() {
}
public static SingleTon getInstance() {
if (singleTonInstance == null) {
synchronized (SingleTon.class) {
System.out.println("Creating instance");
singleTonInstance = new SingleTon();
}
}
return singleTonInstance;
}
@Override
public void run() {
System.out.format("Thread %s is starting\n",Thread.currentThread().getName());
getInstance();
}
}
package SingleTon;
public class SingleTonDemo {
public static void main(String[] args) {
System.out.println("test");
SingleTon t = new SingleTon();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
输出:
test
Thread Thread-0 is starting
Thread Thread-2 is starting
Thread Thread-1 is starting
Creating instance
Creating instance
Creating instance
答案 0 :(得分:5)
您需要更改以下内容:
1)将默认构造函数设为私有。按照书面规定,它是程序包专用的,并且可以由同一程序包中的其他类调用。
private SingleTon() {
}
完成此操作后,您的SingleTonDemo
类将不再能够调用构造函数,而将被迫使用getInstance()
方法。当前,对构造函数的每次调用都在创建一个新实例。
2)同步整个getInstance()
方法。如所写,两个单独的线程可以获得singleTonInstance
的空值,因此每个线程都将创建一个新实例。
public static SingleTon getInstance() {
2a)确保类只有一个实例的另一种方法是使用静态初始化。在静态变量的声明中,创建对象:
private static SingleTon singleTonInstance = new SingleTon();
JVM将确保仅将其调用。这也改善了getInstance方法,因为您将其更改为:
public static SingleTon getInstance() {
return singleTonInstance;
}
这种方法的优点是,不仅getInstance方法更简单,而且不会产生同步开销。
答案 1 :(得分:2)
您实际上是在类之外创建SingleTon
类的实例。使用单例模式时,不应这样做,因为只能创建该类的单个实例。
对于同步问题:
如果所有三个线程都在您的if
方法中输入了getInstance
语句,则每个线程都会创建一个实例(因为if
语句本身未被同步块覆盖)。解决此问题的一种方法是使getInstance
方法同步,即添加synchronized
修饰符。在这种情况下,您的getInstance
方法中的“获取并创建如果不存在”逻辑将作为原子块执行。
答案 2 :(得分:0)
Answer by EJK看起来正确。
这完全是另一种更简单的方法。
Enum
通常来说,用Java实现Singleton
的最好方法是创建一个枚举。与大多数平台相比,Java中的枚举功能更强大,更灵活。参见Tutorial。
Java中的enum
实际上是Enum
的子类,编译器/ JVM处理了这些事情。加载枚举类时,JVM会为每个枚举名称自动实例化一个对象。当然,在单例的情况下,我们只有一个这样的枚举名称。通常,这个名称是INSTANCE
,但可以是您想要的任何名称。
因为Java中的枚举实际上是一个类,所以您可以定义一个构造函数并将对象传递给该构造函数。在下面的示例中,我们为您在“问题”中使用的name
和salary
成员传递初始值。
类似,作为一个类,Java中的枚举可以具有方法。在下面的示例中,我们添加了方法run
来实现Runnable
,就像您在“问题”中所做的一样。
在这里的示例中,我们添加了一个UUID
值作为标识符,这是让您看到确实只有一个单例实例的另一种方式。
package work.basil.example;
import java.time.Instant;
import java.util.UUID;
public enum SingletonDemo implements Runnable {
INSTANCE( "Alice" , 10_000 );
String name;
Integer salary;
UUID uuid;
SingletonDemo ( String name , Integer salary ) {
System.out.println( "DEBUG - Constructing `SingletonDemo` enum at " + Instant.now() );
this.name = name;
this.salary = salary;
this.uuid = UUID.randomUUID();
}
@Override
public void run () {
// CAUTION: Be sure you make this method thread-safe!
System.out.format( "Thread %s is starting. uuid: %s\n" , Thread.currentThread().getName() ,this.uuid.toString() ); // Calling getters as I did here may not be thread-safe without further precautions (synchronization, etc.).
}
public static void main ( String[] args ) {
Thread t1 = new Thread( SingletonDemo.INSTANCE );
Thread t2 = new Thread( SingletonDemo.INSTANCE );
Thread t3 = new Thread( SingletonDemo.INSTANCE );
t1.start();
t2.start();
t3.start();
}
}
运行时。
调试-在2019-01-30T00:48:46.614462Z构造
SingletonDemo
枚举线程线程-0正在启动。 uuid:a2825344-fb15-45d1-a6e4-239fc3bdf3c5
线程Thread-2正在启动。 uuid:a2825344-fb15-45d1-a6e4-239fc3bdf3c5
线程线程1正在启动。 uuid:a2825344-fb15-45d1-a6e4-239fc3bdf3c5
答案 3 :(得分:0)
public class SingleTonDemo {
public static void main(String[] args) {
System.out.println("test");
SingleTon t = SingleTon.getInstance();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
private SingleTon() {
}
答案 4 :(得分:-1)
首先,您的单例构造函数必须为@Controller
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserProfileService userProfileService;
@ModelAttribute("user")
public User getUserModel () {
return userService.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName());
}
@ModelAttribute("userProfile")
public UserProfile getUserProfile() {
return userProfileService.findByUser(userService.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName()));
}
@GetMapping("/")
public String userIndex() {
logger.info("UserIndex");
return "userPage";
}
。其次,将对象用作private
以防止冲突。
mutex
现在,像这样在您的主体中调用public class RunnableSingleton implements Runnable
{
private static volatile RunnableSingleton instance;
private static final Object mutex = new Object();
private RunnableSingleton() {}
public static RunnableSingleton getInstance()
{
RunnableSingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null) {
System.out.println("Creating instance");
instance = result = new RunnableSingleton();
}
}
}
return result;
}
@Override
public void run()
{
System.out.format("Thread %s is starting\n", Thread.currentThread().getName());
getInstance();
}
}
方法:
getInstance()
这是实现的输出:
public class SingletonDemo
{
public static void main(String[] args) {
System.out.println("test");
RunnableSingleton t = RunnableSingleton.getInstance();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}