单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,也是适用于各种语言php,c++,python,等等
1.单例模式有两种
懒汉式:在需要使用对象时才去创建单例对象
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。否则则先执行实例化操作。
饿汉式:在类加载时已经创建好单例对象,等待被程序使用
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
/**
* 懒汉式创建单例对象
*/
private static Singleton singleton;
public Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
Singleton singleton = new Singleton();
}
return singleton;
}
/**
* 饿汉式创建单例对象
*/
private static final Singleton singleton1 = new Singleton();
private Singleton(){}
public static Singleton getInstance1() {
return singleton1;
}
2.在多线程下的单例并不是安全的
如果两个线程同时判断 singleton 为空,那么它们都会去实例化一个Singleton对象,这就变成多例了。所以,我们要解决的是线程安全问题。
// 例子1
public static synchronized Singleton getInstance2() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
// 例子2
public static Singleton getInstance3() {
synchronized(Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
这样就避免了两个线程同时创建Singleton对象的危险性,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,特殊情况下,可能会出现程序卡顿现象。
// 解决了并发问题,和性能问题 因为两次判空,且对类对象加锁
public static Singleton getInstance4() {
if (singleton == null) {
synchronized(Singleton.class) {
if (singleton == null) {
new Singleton();
}
}
}
return singleton;
}
3.破坏单例的两种方式反射和序列化
// 利用反射,强制访问类的私有构造器,去创建另一个对象
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 获取类的构造器
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor();
// 可访问私有构造器
declaredConstructor.setAccessible(true);
// 利用反射构造新对象
Singleton singleton = declaredConstructor.newInstance();
// 通过正常方式获取单例对象
Singleton instance = Singleton.getInstance();
// 通过输出得出结果 false
System.out.print(singleton == instance);
}
// 利用序列化与反序列化破坏单例模式
public static void main1(String[] args) throws IOException, ClassNotFoundException {
// 创建输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
// 将单例对象写到文件中
objectOutputStream.writeObject(Singleton.getInstance());
// 从文件中获取单例对象
File file = new File("Singleton.file");
ObjectInputStream stream = new ObjectInputStream(new FileInputStream(file));
// readObject() 方法读入对象时它必定会返回一个新的对象实例,必然指向新的内存地址。
Singleton singleton = (Singleton) stream.readObject();
// 通过输出得出结果 false
System.out.print(singleton == Singleton.getInstance());
}
4.完美单例写法,规避了反射,被系列化,多线程不安全等问题,那就是枚举Enum
/**
* 使用枚举创建单例
* 1一目了然的代码
* 2天然的线程安全与单一实例
*/
public enum SingletonEnum {
SINGLETON;
SingletonEnum() {
System.out.print("枚举创建对象");
}
public void test() {
SingletonEnum anEnum = SingletonEnum.SINGLETON;
SingletonEnum anEnum1 = SingletonEnum.SINGLETON;
// 地址相同true
System.out.print(anEnum == anEnum1);
}
单例是最简单的设计模式,在工作和面试中都会用到,希望看完对你有一定的帮助