【单例】JAVA中的单例

本文介绍了Java中单例模式的实现,包括饿汉式、懒汉式(线程安全)和通过枚举实现的方式。讨论了它们的特点,如饿汉式的简洁和线程安全性,懒汉式的延迟加载,以及枚举实现的反射安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例。

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

那么如何实现一个单例呢?单例的实现方式有饿汉,懒汉式。懒汉也好,饿汉也好,实现单例的要点就在于让构造方法私有,然后提供公有的静态的方法,对外返回单例的对象实例。懒汉和饿汉的区别在于创建单例对象的时机,一个在类加载时就创建了,一个在调用newInstence方法时才创建。所以,饿汉时实现更为简单,不需要考虑线程安全,但是懒汉式要考虑线程的安全问题。

饿汉式:

public class CatHModel {
    private String name;
    private static final CatHModel cat = new CatHModel("Tom");

    private CatHModel(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public static CatHModel newInstence(String name) {
        return cat;
    }
}

final确保了私有变量cat永远指向一开始创建的名为“Tom"的对象。构造方法私有,无法通过new的方式创建对象。

CatHModel cat = CatHModel.newInstence();
CatHModel cat1 = CatHModel.newInstence();
System.out.println(cat == cat1);

输出为

true

Process finished with exit code 0

可见得到的两个对象地址一致,也就是实际指向的是同一个对象。

懒汉式(线程安全)

public class CatFModel {
    private String name;
    private static CatFModel cat;

    private CatFModel() {
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static CatFModel newInstence() {
        if (cat == null) {
            synchronized (CatFModel.class) {
                if (cat == null) {
                    cat = new CatFModel();
                }
            }
        }
        return cat;
    }
}

我们来测试一下

List<CatFModel> list = Collections.synchronizedList(new ArrayList<>());
int size = 5;
CountDownLatch countDownLatch = new CountDownLatch(size);
for (int i = 0; i < size; i++) {
    final int temp = i;
    new Thread(() -> {
        String name = "Sam" + temp;
        CatFModel catFModel = CatFModel.newInstence();
        catFModel.setName(name);
        list.add(catFModel);
        countDownLatch.countDown();
    }).start();
}
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}
list.forEach(e -> System.out.println(e.getName()));

输出为:

Sam1
Sam1
Sam1
Sam1
Sam1

Process finished with exit code 0

打印的是最后一个线程set的name,因为多个线程使用的是同一个实例,所以最后得到的名字是一样的。多线程下能够保证安全。

但是以上方式实现的单例有一个问题,就是如果通过反射的方式是能够调用私有构造方法创建新对象的。

CatHModel cat = CatHModel.newInstence();
System.out.println(cat.getName());
Constructor constructor = CatHModel.class.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
CatHModel cat2 = (CatHModel) constructor.newInstance("Henry");
System.out.println(cat2.getName());

输出为:

Tom
Henry

Process finished with exit code 0

通过枚举实现单例模式:

public enum CatEnum {
    INSTANCE;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public static CatEnum getInstance(){
        return INSTANCE;
    }
}

再来试一下

List<CatEnum> list = Collections.synchronizedList(new ArrayList<>());
int size = 5;
CountDownLatch countDownLatch = new CountDownLatch(size);
for (int i = 0; i < size; i++) {
    final int temp = i;
    new Thread(
            () -> {
                String name = "Sam" + temp;
                CatEnum catEnum =  CatEnum.getInstance();
                catEnum.setName(name);
                list.add(catEnum);
                countDownLatch.countDown();
            }
    ).start();
}
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}
list.forEach(e -> System.out.println(e.getName()));

输出为

Sam4
Sam4
Sam4
Sam4
Sam4

Process finished with exit code 0

那我们再试一下

Constructor constructor = CatEnum.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
CatEnum cat2 = (CatEnum) constructor.newInstance();
System.out.println(cat2.getName());

输出

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.base/java.lang.reflect.Constructor.acquireConstructorAccessor(Constructor.java:544)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:496)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
    at java_problems.day3.test.main(test.java:67)

会报错,我们看一下具体代码

代码到这里就点不进去了,但是我们看到上面的注释,保证反射的类不是一个枚举类,所以JAVA底层帮助我们做了限制,使得我们无法通过反射的方式破坏单例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值