Java 单例模式 总结整理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 单例模式 总结整理相关的知识,希望对你有一定的参考价值。


    分享总结常见的5种单例模式:


   第一、单例模式的使用场景

    A、Windows的任务管理器、回收站、文件系统如F盘,都是很典型的单例模式  ;

    B、项目中,读取配置文件的类,一般也是单例模式,没有必要每次读取都重新new一个对象加载

    C、数据库的连接池也是单例模式,因为数据库链接是一种数据库资源

    D、在Spring中,每个bean默认都是单例的,可以很方便的交给Spring来管理

    E、在servlet编程中,每个Servlet也是单例模式

   

    还有像360浏览器每次打开都是新的客户端,就不是单例模式;


   第二、单例模式的核心是什么?

    一个类 只有 一个对象实例;

    提供一个访问接口。

    

    // 其实,单例模式,说白了,就是控制一个对象的实例个数

    //比方说,你可以实现 一个类只能存在5个实例个数,超过5个后,就不允许创建,这也是可以实现的。

    

   第三、常见的5种单例模式?

    1、饿汉式(线程安全,调用效率高,非延时加载)

    2、懒汉式(线程安全,调用效率不高,延时加载)

    3、双重检测锁式(此种方式不建议使用)

    4、静态内部类式(线程安全,调用效率高,延时加载)

    5、枚举单例式(线程安全,调用效率高,非延时加载, 可以防止反射,反序列化漏洞)


    第四、具体介绍

    4.1 饿汉式

    4.1.1 优点:

        多线程安全

    4.1.2 问题

        非延迟加载。

         如果加载后,以后都没有使用的话,就白加载了,而且加载过程有可能很耗时。

    4.1.3 代码:      

package com.xingej.patterns.singleton.example1;

/**
 * 单例模式:饿汉式
 * 
 * @author erjun 2017年11月7日 下午7:38:40
 */
public class HungryMethod {

    // static 变量会在类装载时初始化,不会涉及到多线程问题。
    // 虚拟机保证只会装载一次,肯定不会发生并发访问的问题。
    // 因此,可以省略synchronized关键字的
    private static HungryMethod instance = new HungryMethod();

    private HungryMethod() {

    }

    // 问题:如果只是加载了本类,而没有调用getInstance(),甚至永远没有调用,则会造成资源浪费
    public static HungryMethod getInstance() {
        return instance;
    }

}

    4.2 懒汉式

    4.2.1 优点:

        多线程安全、延迟加载、资源利用率提高了

    4.2.2 问题

        并发效率低

         由于:每次调用getInstance()方法,都会同步

    4.2.3 代码: 

    

package com.xingej.patterns.singleton.example2;

/**
 * 懒汉式;延迟加载;
 * 
 * @author erjun 2017年11月7日 下午7:54:33
 */
public class LazyMethod {
    private static LazyMethod instance;

    private LazyMethod() {

    }

    // 方法同步,调用效率低
    public static synchronized LazyMethod getInstacne() {
        if (null == instance) {
            // 只有使用时,才调用
            instance = new LazyMethod();
        }

        return instance;
    }

}

    4.3 双重检测锁式

    4.3.1 优点:

        延迟加载、资源利用率提高了(第一次同步,以后不再同步)

    4.3.2 问题

        由于编译器优化原因,JVM底层内部模型原因,偶尔会出问题,

         因此,不建议使用        

     4.3.3 代码:(注意:还有另外一种写法,就是添加了两个同步模块,但是同样不建议使用)

package com.xingej.patterns.singleton.example3;

/**
 * 双重检测实现单例模式 //-------不建议使用------此方法哦
 * 
 * @author erjun 2017年11月7日 下午8:08:43
 */
public class DoubleCheck {
    private static DoubleCheck instance;

    private DoubleCheck() {

    }

    // 提高了效率,只是在第一次需要同步;以后就不需要同步了
    // 但是
    // 由于编译器优化原因和JVM底层内部模型元素,偶然会出问题。
    // -------不建议使用------
    public static DoubleCheck getInstance() {
        if (null == instance) {
            synchronized (DoubleCheck.class) {
                if (null == instance) {
                    return instance = new DoubleCheck();
                }
            }
        }

        return instance;
    }
}

    4.4 静态内部类

    4.4.1 优点:

        多线程安全、延迟加载

     4.4.2 代码: 

package com.xingej.patterns.singleton.example4;

/**
 * 静态内部类方式创建单例模式
 * 
 * 第一、当你初始化SingletonDemo01的时候,并不会立即加载静态内部类的。 外部类没有static属性,则不会像饿汉式那样立即加载对象。
 * 第二、只有真正调用getiInstance(),才会加载静态内部类。 
 * 第三、加载类时是线程安全的, instance的类型是static final,
 * 从而保证了内存中只有这样一个实例存在,而且只能被赋值一次, 从而保证了线程安全性 具有高并发和延迟加载的优点
 * 
 * @author erjun 2017年11月8日 上午11:38:31
 */
public class StaticInnerClass {
    // 静态内部类,并不会立即加载的,只有调用时才加载的
    private static class SingletonClassInstance {
        private static final StaticInnerClass instance = new StaticInnerClass();
    }

    private StaticInnerClass() {

    }

    public static StaticInnerClass getInstance() {
        return SingletonClassInstance.instance;
    }

}

    4.5 枚举单例模式

    4.5.1 优点:

        实现简单、线程安全

         枚举类本身就是单例模式,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞!

    4.5.2 缺点

        无延迟加载

     4.5.3 代码

        

package com.xingej.patterns.singleton.example5;

/**
 * 枚举的方式 实现 单例模式
 * 
 * 《优点》: 1、实现简单;2、枚举类本身就是单例模式,由JVM从根本上提供保障。避免通过反射和反序列化的漏洞进行创建实例对象 。《缺点》:无延迟加载
 * 
 * 
 * @author erjun 2017年11月8日 上午11:53:40
 */
public enum EnumMethod {
    INSTANCE;

    // 枚举类可以有自己的行为模式的
    //下面这个方法,可以没有的;
    public void show() {
        System.out.println("----我是枚举类型的单例模式哦------");
    }

    // 测试用例
    public static void main(String[] args) {
        EnumMethod aDemo01 = EnumMethod.INSTANCE;
        EnumMethod bDemo01 = EnumMethod.INSTANCE;

        // 校验是否是同一个对象
        System.out.println(aDemo01 == bDemo01);
    }
}


    第五、选择何种的单例模式呢?


    场景一:占用资源少,不需要延时加载:

         枚举类 好于 饿汉式

     场景二占用资源大,需要延时加载

         静态内部类 好于 懒汉式 

     

    第六、多线程方式测试5种单例模式的效率

        

package com.xingej.patterns.singleton;

import java.util.concurrent.CountDownLatch;

import org.junit.Test;

import com.xingej.patterns.singleton.example1.HungryMethod;
import com.xingej.patterns.singleton.example2.LazyMethod;
import com.xingej.patterns.singleton.example3.DoubleCheck;
import com.xingej.patterns.singleton.example4.StaticInnerClass;
import com.xingej.patterns.singleton.example5.EnumMethod;

/**
 * 多线程方式来测试5种单例模式的效率
 * 
 * @author erjun 2017年11月8日 下午4:07:14
 */
public class MulThread {
    private static final int threadNum = 10;

    private static final int count = 10 * 10000; // 调用单例 对象 10万次

    private static CountDownLatch countDownLatch = new CountDownLatch(threadNum);

    // 饿汉式 效率测试
    @Test
    public void testEHanShi() throws Exception {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int j = 0; j < count; j++) {
                        HungryMethod.getInstance();
                    }

                    countDownLatch.countDown();
                }
            }).start();
        }

        // 其实,所有的这些阻塞,内部其实都有一个死循环去监控的
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println("----饿汉式---执行时间---:\t" + (endTime - startTime) + " 毫秒");

    }

    // 懒汉式 效率测试
    @Test
    public void testLanHanShi() throws Exception {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int j = 0; j < count; j++) {
                        LazyMethod.getInstacne();
                    }

                    countDownLatch.countDown();
                }
            }).start();
        }

        // 其实,所有的这些阻塞,内部其实都有一个死循环去监控的
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println("----懒汉式---执行时间---:\t" + (endTime - startTime) + " 毫秒");

    }

    // 双重检测 效率测试
    @Test
    public void test2Check() throws Exception {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int j = 0; j < count; j++) {
                        DoubleCheck.getInstance();
                    }

                    countDownLatch.countDown();
                }
            }).start();
        }

        // 其实,所有的这些阻塞,内部其实都有一个死循环去监控的
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println("----双重检测---执行时间---:\t" + (endTime - startTime) + " 毫秒");

    }

    // 静态内部类 单例模式 效率测试
    @Test
    public void testStaticInnerClass() throws Exception {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int j = 0; j < count; j++) {
                        StaticInnerClass.getInstance();
                    }

                    countDownLatch.countDown();
                }
            }).start();
        }

        // 其实,所有的这些阻塞,内部其实都有一个死循环去监控的
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println("----静态内部类方式---执行时间---:\t" + (endTime - startTime) + " 毫秒");

    }

    // 枚举类方式的单例模式 效率测试
    @Test
    public void testEnumMethod() throws Exception {

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int j = 0; j < count; j++) {
                        // -------------------------------注意-------------------------------
                        // ----枚举类,必须有接收值,就是等式左边这些Object object, 不然有问题哦
                        Object object = EnumMethod.INSTANCE;
                    }

                    countDownLatch.countDown();
                }
            }).start();
        }

        // 其实,所有的这些阻塞,内部其实都有一个死循环去监控的
        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println("----枚举类方式---执行时间---:\t" + (endTime - startTime) + " 毫秒");

    }

}

   

    第七、实际案例:使用单例模式,读取配置文件

代码:

package com.xingej.patterns.singleton.example6;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class SingleMode {

    private SingleMode() {
        // 创建对象时,会默认加载配置文件
        readConfig();
    }

    private static class InnerSingle {
        private static SingleMode instance = new SingleMode();
    }

    public static SingleMode getInstance() {
        return InnerSingle.instance;
    }

    // ---------------------上面是单例模式----------------------------
    private Properties properties;

    private InputStream inputStream = null;

    private void readConfig() {
        properties = new Properties();

        try {
            // 注意,配置文件的路径
            inputStream = SingleMode.class.getResourceAsStream("/single.properties");

            properties.load(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 提供对外的接口,
    // 查询配置文件的属性信息
    public String getProperties(String key) {
        return properties.getProperty(key);
    }
}

测试:

package com.xingej.patterns.singleton.example6;

import org.junit.Test;

public class SingleModeTest {

    // 通过单例方式,来读取配置文件
    @Test
    public void testReadConfigBySingleMothod() {
        SingleMode instance = SingleMode.getInstance();

        String name = instance.getProperties("name");

        System.out.println("---name:\t" + name);
    }

}


总结:

    设计模式一定抛开代码,重点在思想

    想想现实生活中,哪些是单例模式形式存在的



-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------

下面这些内容,

可以不用关心,

一般情况下,不会用到的;


如果想扩展知识,可以看一下。

上面提到的5种单例模式中,除了枚举单例模式外,其他4种单例模式都有bug的,

也就是说,可以通过

反射的方式

或者

反序列化的方式 

创建两个以上的对象,从而打破了单例模式只有一个对象的限定。


第一、反射的方式 破解单例模式的限定


添加测试用例,选择懒汉式单例模式进行测试

    技术分享从上面的结果中,看出来了,这个类,存在两个对象了。


如何反破解呢?

    技术分享


第二、反序列化的方式 破解单例模式的限定

使用反序列化方式的话,

前提条件时,单例模式必须实现Serializable接口,不然不能序列化的。

测试用例:

技术分享


如何反破解呢?

只需要在单例模式内,添加一个私有方法readResolve()即可。

具体代码如下:

package com.xingej.patterns.singleton.example4;

import java.io.Serializable;

/**
 * 测试 通过 反序列化的方式,来破解单例模式
 * 
 * @author erjun 2017年11月8日 下午3:41:29
 */
public class StaticInnerClass2 implements Serializable {

    private static final long serialVersionUID = 1L;

    // 静态内部类,并不会立即加载的,只有调用时才加载的
    private static class SingletonClassInstance {
        private static final StaticInnerClass2 instance = new StaticInnerClass2();
    }

    private StaticInnerClass2() {

    }

    public static StaticInnerClass2 getInstance() {
        return SingletonClassInstance.instance;
    }

    // 反序列化时,自动调用这个方法的
    // 直接返回对象,不需要再单独创建新的对象了

    // 使用下面的方式,就可以
    // 阻止,通过序列化的方式来破解单例模式
    private Object readResolve() {
        return SingletonClassInstance.instance;
    }

}



代码已上传到git上:

https://github.com/xej520/xingej-design-patterns






















本文出自 “XEJ分布式工作室” 博客,请务必保留此出处http://xingej.blog.51cto.com/7912529/1980184

以上是关于Java 单例模式 总结整理的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式知识整理

历史最全单例模式总结

设计模式总结(Java)

设计模式总结(Java)

JAVA知识总结:单例模式和多态

Java中的单例模式