SingleTon类未同步并返回多个实例

时间:2019-01-30 00:27:12

标签: java multithreading

下面的代码有问题。从同一进程的多个线程调用同一个单例类的多个实例。我希望程序只打印一次“正在创建实例”,但它打印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

5 个答案:

答案 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中的枚举实际上是一个类,所以您可以定义一个构造函数并将对象传递给该构造函数。在下面的示例中,我们为您在“问题”中使用的namesalary成员传递初始值。

类似,作为一个类,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();
    }
}