单例模式

300 阅读7分钟

引言:更多相关请看 Java其它
概述:保证一个类中只有一个实例、提供对外访问的方法。
组成:私有化构造方法、私有静态属性、公共静态方法(获取对象)。
应用场景:Servlet、日志管理、数据库连接池、Window系统的任务浏览器。
优点:应用启动直接创建一个对象长驻内存,减少系统性能开销。
分类:

饿汉式:线程安全、效率高、不能延时加载。
懒汉式:线程安全、效率低、能延时加载(lazy load)。
双重检测锁模式:因为编译器优化和JVM底层内部模型原理,偶尔出现问题,不建议使用。
静态内部类式:线程安全、效率高、能延时加载。
枚举单例:线程安全、效率高、不能延时加载。

适用场景:单例对象占用资源小、无需延迟加载(单例好于饿汉式),单例对象占用资源大、需要延迟加载(静态内部类好于懒汉式)。

饿汉式

概述:线程安全、效率高、不能延时加载。一开始就创建对象,天然安全,不需要加同步。
示例:

class TestSingleton {
    //类初始化时马上加载对象
    private static volatile TestSingleton instance = new TestSingleton();
    //私有化构造
    private TestSingleton(){}
    //对外访问方式  不需要加同步,类初始化加天然安全
    public static TestSingleton getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(TestSingleton.getInstance());
        System.out.println(TestSingleton.getInstance());
    }
}

懒汉式

概述:线程安全、效率低、能延时加载(lazy load)。等需要时再创建对象,加synchronized保证安全(避免重复创建对象)。
示例:

class TestSingleton {
    //私有静态属性
    private static volatile TestSingleton instance;

    //私有构造
    private TestSingleton() {
    }

    //同步公共静态方法 加同步避免重复创建对象
    public static synchronized TestSingleton getInstance() {
        if (instance == null) {//如果为空,才让它创建
            instance = new TestSingleton();//需要对象时才创建
        }
        //返回对象
        return instance;
    }


    public static void main(String[] args) {
        System.out.println(TestSingleton.getInstance());
        System.out.println(TestSingleton.getInstance());
    }
}

双重检测锁式

概述:DCL Double Checking Locking。因为编译器优化和JVM底层内部模型原理,偶尔出现问题,不建议使用。把同步(可重入锁)放到方法里面,保证对象只有第一次创建的时候才需要同步,提高性能。
示例:

class TestSingleton {
    private volatile static TestSingleton instance;

    private TestSingleton() {
    }

    public static TestSingleton getInstance() {
        // 第一次检测
        if (instance == null) {
            synchronized (TestSingleton.class) {
                // 第二次检测
                if (instance == null) {
                    instance = new TestSingleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(TestSingleton.getInstance());
        System.out.println(TestSingleton.getInstance());
    }
}

静态内部类式

概述:线程安全、效率高、能延时加载。静态内部类属性是对对外部类的引用(实例在内部类创建),内部类的属性加static、final,保证对象只有一个,只能被赋值一次。
示例:

class TestSingleton {
    //静态内部类
    private static class SingleClass {
        //建立对外部类的引用 加静态私有保证只有一个对象,只能被赋值一次
        private static final TestSingleton instance = new TestSingleton();
    }

    //私有构造
    private TestSingleton() {
    }

    //提供公共访问方式
    public static TestSingleton getInstance() {
        return SingleClass.instance;
    }

    public static void main(String[] args) {
        System.out.println(TestSingleton.getInstance());
        System.out.println(TestSingleton.getInstance());
    }
}

枚举式

概述:线程安全、效率高、不能延时加载。枚举本身就是一个单例,JVM从根本保证反射和反序列化的漏洞。缺点是不能延时加载。
注意:枚举属性默认修饰符public static final。
示例:

public enum TestSingleTon {
    instance;//这个枚举元素本身就是单例对象
}

class TestEnum {
    public static void main(String[] args) {
        //测试
        System.out.println(TestSingleTon.instance == TestSingleTon.instance);//true
    }
}

反射和反序列化漏洞(除了枚举)

单例有漏洞(除枚举外),可通过反射和反序列化可能造成对象不只有一个。

反射漏洞

以懒汉式演示漏洞:

public class TestSingleton {
    //私有静态属性
    private static TestSingleton instance;

    //私有构造
    private TestSingleton() {
    }

    //同步公共静态方法 加同步避免重复创建对象
    public static synchronized TestSingleton getInstance() {
        if (instance == null) {//如果为空,才让它创建
            instance = new TestSingleton();//需要对象时才创建
        }
        //返回对象
        return instance;
    }

    // 编写反射漏洞测试:
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        TestSingleton a = TestSingleton.getInstance();
        TestSingleton b = TestSingleton.getInstance();
        System.out.println("静态方法获取对象:" + a);
        System.out.println("静态方法获取对象:" + b);
        System.out.println("====================");

        // 反射破解
        Class<TestSingleton> clazz = (Class<TestSingleton>) Class.forName("com.example.demo.test01.TestSingleton");
        Constructor<TestSingleton> cons = clazz.getDeclaredConstructor();
        cons.setAccessible(true);// 获取私有方法
        TestSingleton c = cons.newInstance();
        TestSingleton d = cons.newInstance();
        System.out.println("反射创建对象:" + c);
        System.out.println("反射创建对象:" + d);
    }
}

反射创建对象的结果不一致:

静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反射创建对象:com.example.demo.test01.TestSingleton@19469ea2
反射创建对象:com.example.demo.test01.TestSingleton@13221655

解决:在懒汉式类里面添加(如果对象不为空,想创建对象就会抛出异常)

//私有构造
private TestSingleton() {
    if (instance != null) {
        throw new RuntimeException();// 防止反射漏洞,避免通过构造方法创建对象
    }
}

完整代码:

public class TestSingleton {
    //私有静态属性
    private static TestSingleton instance;

    //私有构造
    private TestSingleton() {
        if (instance != null) {
            throw new RuntimeException();// 防止反射漏洞,避免通过构造方法创建对象
        }
    }

    //同步公共静态方法 加同步避免重复创建对象
    public static synchronized TestSingleton getInstance() {
        if (instance == null) {//如果为空,才让它创建
            instance = new TestSingleton();//需要对象时才创建
        }
        //返回对象
        return instance;
    }

    // 编写反射漏洞测试:
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        TestSingleton a = TestSingleton.getInstance();
        TestSingleton b = TestSingleton.getInstance();
        System.out.println("静态方法获取对象:" + a);
        System.out.println("静态方法获取对象:" + b);
        System.out.println("====================");

        // 反射破解
        Class<TestSingleton> clazz = (Class<TestSingleton>) Class.forName("com.example.demo.test01.TestSingleton");
        Constructor<TestSingleton> cons = clazz.getDeclaredConstructor();
        cons.setAccessible(true);// 获取私有方法
        TestSingleton c = cons.newInstance();
        TestSingleton d = cons.newInstance();
        System.out.println("反射创建对象:" + c);
        System.out.println("反射创建对象:" + d);
    }
}

效果:(不让通过构造方法创建对象,直接抛出异常)

静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.example.demo.test01.TestSingleton.main(TestSingleton.java:38)
Caused by: java.lang.RuntimeException
	at com.example.demo.test01.TestSingleton.<init>(TestSingleton.java:13)
	... 5 more

反序列化漏洞

测试反序列化:(先给懒汉式类加了实现序列化接口)

// 实现序列化接为了将对象序列化
public class TestSingleton implements Serializable {
    //私有静态属性
    private static TestSingleton instance;

    //私有构造
    private TestSingleton() {
    }

    //同步公共静态方法 加同步避免重复创建对象
    public static synchronized TestSingleton getInstance() {
        if (instance == null) {//如果为空,才让它创建
            instance = new TestSingleton();//需要对象时才创建
        }
        //返回对象
        return instance;
    }

    // 编写反序列化漏洞测试:
    public static void main(String[] args) throws Exception {
        TestSingleton a = TestSingleton.getInstance();
        TestSingleton b = TestSingleton.getInstance();
        System.out.println("静态方法获取对象:" + a);
        System.out.println("静态方法获取对象:" + b);
        System.out.println("====================");

        // 反序列化破解
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
        oos.writeObject(TestSingleton.getInstance());// 序列化
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
        // 反序列化
        Object c = ois.readObject();
        System.out.println("反序列化创建对象:" + c);
        ois.close();
    }
}

效果:

静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反序列化创建对象:com.example.demo.test01.TestSingleton@17f052a3

解决(在懒汉式类里添加readResolve方法):

// 防止反序列化漏洞
public Object readResolve() {
    return instance;
}

再来看结果(就是一个对象):

静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反序列化创建对象:com.example.demo.test01.TestSingleton@3af49f1c

注意:个人推测是因为ois.readObject()底层会调用readResolve()去创建对象,所以我们重写readResolve()时会直接调用重写的readResolve()并通过return instance;返回单独的实例。

五种单例的效率对比

优先看测试结果:

可得执行时长:枚举式<饿汉式<双重检测式<静态内部类式<懒汉式。
测试示例:

/**
 * 懒汉式
 */
class TestSingleton1 {
    //类初始化时马上加载对象
    private volatile static TestSingleton1 instance = new TestSingleton1();

    //私有化构造
    private TestSingleton1() {
    }

    //对外访问方式  不需要加同步,类初始化加天然安全
    public static TestSingleton1 getInstance() {
        return instance;
    }
}

/**
 * 懒汉式
 */
class TestSingleton2 {
    //私有静态属性
    private volatile static TestSingleton2 instance;

    //私有构造
    private TestSingleton2() {
    }

    //同步公共静态方法 加同步避免重复创建对象
    public static synchronized TestSingleton2 getInstance() {
        if (instance == null) {//如果为空,才让它创建
            instance = new TestSingleton2();//需要对象时才创建
        }
        //返回对象
        return instance;
    }
}


/**
 * 双重检测锁式DCL Double Checking Lock
 */
class TestSingleton3 {
    private volatile static TestSingleton3 instance;

    private TestSingleton3() {
    }

    public static TestSingleton3 getInstance() {
        // 第一次检测
        if (instance == null) {
            synchronized (TestSingleton3.class) {
                // 第二次检测
                if (instance == null) {
                    instance = new TestSingleton3();
                }
            }
        }
        return instance;
    }
}

/**
 * 静态内部类式
 */
class TestSingleton4 {
    //静态内部类
    private static class SingleClass {
        //建立对外部类的引用 加静态私有保证只有一个对象,只能被赋值一次
        private static final TestSingleton4 instance = new TestSingleton4();
    }

    //私有构造
    private TestSingleton4() {
    }

    //提供公共访问方式
    public static TestSingleton4 getInstance() {
        return SingleClass.instance;
    }
}

enum TestSingleTon5 {
    instance;//这个枚举元素本身就是单例对象
}

public class TestSLT {
    public static void main(String[] args) throws InterruptedException {
        //开始
        long start = System.currentTimeMillis();
        //同步辅助类 ,可以让一个或者多个线程等待一组操作完成之后执行
        final CountDownLatch count = new CountDownLatch(10);//
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000000; j++) {
//                        Object o = TestSingleton1.getInstance();//饿汉式 时间:24
//                        Object o = TestSingleton2.getInstance();//懒汉式 时间:430
//                        Object o = TestSingleton3.getInstance();//双重检测式 时间:27
//                        Object o = TestSingleton4.getInstance();//静态内部类式 时间:36
                        Object o = TestSingleTon5.instance;//枚举式 时间:18
                    }
                    count.countDown();//计数减一
                }
            }).start();
        }
        count.await();//必须等线程操作完成,main线程才能开始
        long end = System.currentTimeMillis();//结束
        System.out.println("花费时间:" + (end - start));
    }

}