跟萌新一起学设计模式(一)之单例模式

单例模式 同时被 3 个专栏收录
1 篇文章 0 订阅
15 篇文章 0 订阅
15 篇文章 0 订阅

**

设计模式(一)之单例模式

**

  • 前言说明

    本人工作一段时间后,无意间在B站看完了韩顺平老师的设计模式课程,深感自己的不足,故借助课程与自己之前工作遇到的实例来进行设计模式课程的学习和巩固。

  • 单例模式的特点:对某个类只能存在一个对象实例,并且该类只提供一个取得其唯一对象实例的(静态)方法

  • 创建单例模式的八种方式

    1、饿汉式(静态常量,线程安全)

class Singleton01{
    /**
     *  私有化构造器
     */
    private Singleton01(){}

    /**
     *  静态常量
     */
    private final static Singleton01 INSTANCE = new Singleton01();

    /**
     *  外部只能通过此方法拿到实例
     */
    public static Singleton01 getInstance(){
        return INSTANCE;
    }
}

  测试一下线程是否安全和是否为单例,这里用hashCode是否一样来判断。代码中是用线程池中的10个线程同时去调用方法创建实例。

public class Singleton01Test {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(7,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
        // 10个线程一起拿到测试类的实例,并打印各自的hashCode
        for (int i = 0; i < 10; i++) {
            pool.execute(() -> {
                Singleton01 instance = Singleton01.getInstance();
                System.out.println(instance.hashCode());
            });
        }
        pool.shutdown();
    }
}

  多次打印后每次的结果皆一致,说明线程安全,每次拿到的都是同一个实例。但是此方法在需要使用实例前就已经创建了实例,浪费内存空间。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=64602:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton01Test
2110126355
2110126355
2110126355
2110126355
2110126355
2110126355
2110126355
2110126355
2110126355
2110126355

Process finished with exit code 0

  2、饿汉式(静态代码块,线程安全)

class Singleton02 {

    /**
     * 私有化构造器
     */
    private Singleton02() {
    }

    private final static Singleton02 INSTANCE;

    static {
        INSTANCE = new Singleton02();
    }

    /**
     * 外部只能通过此方法拿到实例
     */
    public static Singleton02 getInstance() {
        return INSTANCE;
    }
}

  同样的测试结果,线程安全,每次拿到的都是同一个实例,与第一种相比其实没有什么大的区别,都是在需要使用实例前就已经创建了实例,浪费内存空间。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=64884:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton02Test
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494

Process finished with exit code 0

  3、懒汉式(原始写法,线程不安全)

class Singleton03 {

    /**
     * 私有化构造器
     */
    private Singleton03() {
    }

    private static Singleton03 singleton03;

    /**
     * 外部只能通过此方法拿到实例,调用该方法时才创建实例
     */
    public static Singleton03 getInstance() {
        if(singleton03 == null){
            singleton03 = new Singleton03();
        }
        return singleton03;
    }
}

  测试发现线程不安全,因为有可能多个线程同时进入了if中去创建了不同的实例,故不推荐这种写法。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65031:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton03Test
1876038113
2110126355
1876038113
1876038113
1876038113
1482625110
1876038113
1876038113
1876038113
1876038113

Process finished with exit code 0

  4、懒汉式(同步方法,线程安全)

class Singleton04 {

    /**
     * 私有化构造器
     */
    private Singleton04() {
    }

    private static Singleton04 singleton04;

    /**
     * 外部只能通过此方法拿到实例,调用该方法时才创建实例
     */
    public static synchronized Singleton04 getInstance() {
        if(singleton04 == null){
            singleton04 = new Singleton04();
        }
        return singleton04;
    }
}

  在获取实例的方法上加了synchronized 锁之后,就变成线程安全的了,缺点就是整个方法都加了锁,所有没进到方法中的线程都要在方法外阻塞,效率低。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65078:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton04Test
1153458781
1153458781
1153458781
1153458781
1153458781
1153458781
1153458781
1153458781
1153458781
1153458781

Process finished with exit code 0

  5、懒汉式(同步代码块,线程不安全)

class Singleton05 {

    /**
     * 私有化构造器
     */
    private Singleton05() {
    }

    private static Singleton05 singleton05;

    /**
     * 外部只能通过此方法拿到实例,调用该方法时才创建实例
     */
    public static Singleton05 getInstance() {
        if(singleton05 == null){
            synchronized (Singleton05.class){
                singleton05 = new Singleton05();
            }
        }
        return singleton05;
    }
}

  或许有人为了提高上一种方法的效率,会只在创建实例的方法外面加上同步代码块,但是只要多个线程进入了if中,不管进去后需不需要排队阻塞,都会创建不同的实例,故此方法不能用。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65172:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton05Test
907222048
535248348
535248348
535248348
535248348
535248348
535248348
1674877494
535248348
535248348

Process finished with exit code 0

  6、双重检查(同步代码块,线程安全)

class Singleton06 {

    /**
     * 私有化构造器
     */
    private Singleton06() {
    }

    private volatile static Singleton06 singleton06;

    /**
     * 外部只能通过此方法拿到实例,调用该方法时才创建实例
     */
    public static Singleton06 getInstance() {
        if(singleton06 == null){
            synchronized (Singleton06.class){
                if(singleton06 == null){
                    singleton06 = new Singleton06();
                }
            }
        }
        return singleton06;
    }
}

  此方法是对上一种方法的改进,也是只在创建实例的方法外面加上同步代码块,但是创建实例之前会先判断是否已经创建过实例,故此方法线程安全,效率也较高,推荐使用。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65233:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton06Test
1482625110
1482625110
1482625110
1482625110
1482625110
1482625110
1482625110
1482625110
1482625110
1482625110

Process finished with exit code 0

  7、静态内部类(线程安全)

class Singleton07 {

    /**
     * 私有化构造器
     */
    private Singleton07() {
    }

    /**
     * 外部只能通过此方法拿到实例,调用该方法时才创建实例
     */
    private static class Singleton07Get{
        private static final Singleton07 INSTANCE = new Singleton07();
    }
    public static Singleton07 getInstance() {
        return Singleton07Get.INSTANCE;
    }
}

  这种方式采用了类装载的机制来保证初始化实例时只有一个线程,静态内部类方式在Singleton07类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载Singleton07Get类,从而完成Singleton07的实例化。利用了jvm保证线程安全,效率高,推荐使用。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65388:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton07Test
907222048
907222048
907222048
907222048
907222048
907222048
907222048
907222048
907222048
907222048

Process finished with exit code 0

  8、枚举(线程安全)

enum Singleton08 {
    /**
     * Effective Java作者Josh Bloch 提倡的方式
     */
    INSTANCE
}

  这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。线程安全且效率高,推荐使用。

D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=65448:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes com.wd.singleton.Singleton08Test
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494
1674877494

Process finished with exit code 0
  • 总结
      不推荐使用种第3、5种方式来实现单例模式,因为线程不安全;推荐使用第6、7、8种方式来实现单例模式,因为线程安全且效率高;第1、2、4种方式也可以使用,只是第1种和第2种会浪费内存,第4种的效率低(如JDK中的Runtime就是用的这种方式来实现单例模式)。
  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值