单例模式(对象私有化)

单例模式(对象私有化)

目录

单例模式(对象私有化)

1.介绍

应⽤实例:

优点:

缺点:

使⽤场景:

2.实现

3 单例模式实现⽅式

3.1 饿汉式

3.2 懒汉式

3.3 双检锁/双重校验锁

3.4 登记式/静态内部类

3.5 枚举式


单例模式(Singleton Pattern)是Java中最简单的设计模式之⼀。这种类型的设计模式属于创建型模式,它提供了 ⼀种创建对象的最佳⽅式。这种模式涉及到⼀个单⼀的类,该类负责创建⾃⼰的对象,同时确保只有单个对象被创建。这个类提供了⼀种访问其唯⼀的对象的⽅式,可以直接访问,不需要实例化该类的对象

注意:

1.单例类只能有⼀个实例。

2.单例类必须⾃⼰创建⾃⼰的唯⼀实例。

3.单例类必须给所有其他对象提供这⼀实例。

1.介绍

意图:保证⼀个类仅有⼀个实例,并提供⼀个访问它的全局访问点。

主要解决:⼀个全局使⽤的类频繁地创建与销毁。

何时使⽤:当你想控制实例数⽬,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应⽤实例:

1.多个进程或线程同时操作⼀个⽂件,所以所有⽂件的处理必须通过唯⼀的实例进⾏。

2.⼀些设备管理器常常设计为单例模式,⽐如⼀个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同⼀个⽂件。

优点:

1.在内存⾥只有⼀个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

2.避免对资源的多重占⽤(⽐如写⽂件操作)。

缺点:

没有接⼝,不能继承,与单⼀职责原则冲突,⼀个类应该只关⼼内部逻辑,⽽不关⼼外⾯怎么样来实例化。

使⽤场景:

1.要求⽣产唯⼀序列号。

2.WEB中的计数器,不⽤每次刷新都在数据库⾥加⼀次,⽤单例先缓存起来。

3.创建的⼀个对象需要消耗的资源过多,⽐如I/O与数据库的连接等。

2.实现

我们将创建⼀个SingleObject类。SingleObject类有它的私有构造⽅法和本身的⼀个静态实例。 SingleObject类提供了⼀个静态⽅法,供外界获取它的静态实例。

3 单例模式实现⽅式

3.1 饿汉式

提前将对象准备好,加载到内存中

3.1.1 饿汉式介绍

是否Lazy初始化:否 (提前准备好对象)

是否多线程安全:是 (饿汉式只有一个实例,没有多线程)

描述:这种⽅式⽐较常⽤,但容易产⽣垃圾对象。 (可能创建出来的对象一直没有用)

优点:没有加锁,执⾏效率会提⾼。

缺点:类加载时就初始化,浪费内存。

它基于classloader机制避免了多线程的同步问题,不过instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中⼤多数都是调⽤getInstance()⽅法, 但是也不能确定有其他的⽅式(或者其他的静态⽅法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

3.1.2 饿汉式案例

1.创建⼀个Java项⽬。

2.创建⼀个HungrySingleton类。

package com.设计模式.饿汉式;
public class HungrySingleton {
    //设置为静态,实现全局访问              //提前将对象准备好,加载到内存中
    private static HungrySingleton instance = new HungrySingleton();
    //构造方法私有化目的是为了在其他类里不能创建该对象
    private HungrySingleton(){}
    //提供静态方法来访问类中的私有成员
    public static HungrySingleton getInstance(){
        return instance;
    }
    public void showMessage(){
        System.out.println("创建instence成功,showMessage执行了");
    }
}

3.从HungrySingleton类获取唯⼀的对象。

package com.设计模式.饿汉式;
public class Test_HungrySingleton {
    public static void main(String[] args) {
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        hungrySingleton.showMessage();
    }
}

4.执⾏程序,输出结果。

3.2 懒汉式

什么时候使用对象,什么时候创建对象

3.2.1 懒汉式线程不安全

3.2.1.1 懒汉式线程不安全介绍

是否Lazy初始化:是 (懒汉式是在对象真正需要被实例化的时候,才去创建对象)

是否多线程安全:否 (多线程同时进行到if时,可能创建多个instance)

描述:这种⽅式是最基本的实现⽅式,这种实现最⼤的问题就是不⽀持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。

这种⽅式lazy loading很明显,不要求线程安全,在多线程不能正常⼯作。

3.2.1.2 懒汉式线程不安全案例

1.创建⼀个LazySingleton类。

package com.设计模式.懒汉式.线程不安全;
    /*
        什么时候使用对象,什么时候创建对象  (懒加载)
     */
public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton (){}
     public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
     }
     public void showMessage(){
         System.out.println("创建instance成功,showMessage执行了");
     }
}

2.从LazySingleton类获取唯⼀的对象。

package com.设计模式.懒汉式.线程不安全;
import com.设计模式.懒汉式.线程不安全.LazySingleton;
public class Test_LazySingleton {
    public static void main(String[] args) {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        lazySingleton.showMessage();
    }
}

3.执⾏程序,输出结果。

3.2.2 懒汉式线程安全

3.2.2.1 懒汉式线程安全介绍

是否Lazy初始化:是

是否多线程安全:是 (加了锁)

描述:这种⽅式具备很好的lazy loading,能够在多线程中很好的⼯作,但是效率很低,99%情况下不需要同步。

优点:第⼀次调⽤才初始化,避免内存浪费

缺点:必须加锁synchronized才能保证单例,但加锁会影响效率

getInstance()的性能对应⽤程序不是很关键(该⽅法使⽤不太频繁)。

3.2.2.2 懒汉式线程安全案例

对原来类中的getInstance()方法⽤synchronized修饰。

package com.设计模式.懒汉式.线程安全;
/*
    什么时候使用对象,什么时候创建对象
 */
public class LazySingleton {
        private static LazySingleton instance;
        private LazySingleton (){}
    //加锁变成多线程安全
        public static synchronized LazySingleton getInstance(){
                if (instance == null){
                        instance = new LazySingleton();
                }
                return instance;
        }
        public void showMessage(){
                System.out.println("创建instance成功,showMessage执行了");
        }
}

3.3 双检锁/双重校验锁

处在懒汉式的基础上,比懒汉式更高效

3.3.1 双检锁/双重校验锁介绍

是否Lazy初始化:是

是否多线程安全:是

描述:双检锁/双重校验锁(DCL,即double-checked locking),这种⽅式采⽤双锁机制,安全且在多线程情况下能保持⾼性能。

getInstance()的性能对应⽤程序很关键。

3.3.2 双检锁/双重校验锁案例

volatile是⼀个特征修饰符(type specifier)。volatile的作⽤是作为指令关键字,在指令层面保证当前对象的值不会被误读或恶意修改,确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值。简单地说就是防⽌编译器对代码进⾏优化。

1.创建⼀个DCLSingleton类。

package com.设计模式.双检锁;
public class DCLSingleton {
    /*
    volatile:
        修饰类的属性的
        在指令层面保证当前对象的值不会被误读或恶意修改
        作用:确保本条指令所修饰的成员不会在编译的时候被省略,且每次都会直接读取它的值
        防止编译器优化此代码的风险
     */
    private volatile static DCLSingleton instance;
    private DCLSingleton (){}
    public static synchronized DCLSingleton getInstance(){
        if (instance == null){
            synchronized (DCLSingleton.class){
                if (instance == null){
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
    public void showMessage(){
        System.out.println("创建instance成功,showMessage执行了");
    }
}
​

2.从DCLSingleton类获取唯⼀的对象。

package com.设计模式.双检锁;
public class Test_DCLSingleton {
    public static void main(String[] args) {
        DCLSingleton dclSingleton = DCLSingleton.getInstance();
        dclSingleton.showMessage();
    }
}

3.执⾏程序,输出结果。

3.4 登记式/静态内部类

内部类只有在外部类被调用才加载,产生INSTANCE实例,又不用加锁,此模式有懒汉式 和饿汉式的优点,屏蔽了他们的缺点,是最好的单例模式

3.4.1 登记式/静态内部类介绍

是否Lazy初始化:是 (在一开始的时候内部类不会被加载,内部类只有被调用的时候才会被加载)

是否多线程安全:是 (classloader机制为其自动加了锁)

描述:这种⽅式能达到双检锁⽅式⼀样的功效,但实现更简单。对静态域使⽤延迟初始化,应使⽤这种⽅式⽽不是双检锁⽅式。这种⽅式只适⽤于静态域的情况,双检锁⽅式可在实例域需要延迟初始化时使⽤。 这种⽅式同样利⽤了classloader机制来保证初始化instance时只有⼀个线程,它跟饿汉式不同的是:饿汉式只要HungrySingleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),⽽这种⽅式是RegisterSingleton类被装载了,instance不⼀定被初始化。因为Test_RegisterSingleton类没有被主动使⽤,只有通过显式调⽤getInstance⽅法时,才会显式装载SingletonHolder类,从⽽实例化instance。想象⼀下,如果实例化instance很消耗资源,所以想让它延迟加载,另外⼀⽅⾯,还要保证多线程安全,因为不能确保Singleton类还可能在其他的地⽅被主动使⽤从⽽被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种⽅式相⽐饿汉式⽅式就显得很合理。

3.4.2 登记式/静态内部类案例

1.创建⼀个RegisterSingleton类。

package com.设计模式.登记式;
    /*
    * 登记式/静态内部类:
    * 1.在单例类中声明一个静态内部类
    * 2.在这个静态内部类中声明单例对象,并且初始化这个对象
    * */
public class RegisterSingleton {
    private static class SingletonHolder {
        //创建私有化的静态常量
        private static final RegisterSingleton INSTANCE = new RegisterSingleton();
    }
        private RegisterSingleton (){}
        //final修饰的方法不能被重写
        public static final RegisterSingleton getInstance(){
        return SingletonHolder.INSTANCE;
        }
        public void showMessage(){
            System.out.println("INSTANCE创建成功,showMessage执行了");
        }
}

2.从RegisterSingleton类获取唯⼀的对象。

package com.设计模式.登记式;
​
public class Test_RegisterSingleton {
    public static void main(String[] args) {
        RegisterSingleton registerSingleton = RegisterSingleton.getInstance();
        registerSingleton.showMessage();
    }
}

3.执⾏程序,输出结果。

3.5 枚举式

3.5.1 枚举式介绍

是否Lazy初始化:否

是否多线程安全:是

描述:这种实现⽅式还没有被⼴泛采⽤,但这是实现单例模式的最佳⽅法。它更简洁,⾃动⽀持序列化机制,绝对防⽌多次实例化。 这种⽅式是Effective Java作者Josh Bloch提倡的⽅式,它不仅能避免多线程同步问题,⽽且还⾃动⽀持序列化机 制,防⽌反序列化重新创建新的对象,绝对防⽌多次实例化。不过由于JDK1.5后才加⼊enum特性,⽤这种⽅式写 不免让⼈感觉⽣疏,在实际⼯作中,也很少⽤。 不能通过reflection attack来调⽤私有构造⽅法。

3.5.2 枚举式案例

1.创建⼀个EnumSingleton枚举类。

package com.设计模式.枚举式;
​
public enum EnumSingleton {
    //枚举常量可以作为枚举内部定义方法的访问
    INSTANCE;
    /*
    * 1.枚举类型中定义的变量只能是常量
    * 2.枚举类型中定义方法(和class类结构中定义的方法完全一样)
    * */
    public void showMessage(){
        System.out.println("EnumSingleton类中的showMessage");
    }
}

2.从EnumSingleton枚举类获取唯⼀的枚举类型数据。

package com.设计模式.枚举式;
​
public class Test_EnumSingleton {
    public static void main(String[] args) {
        EnumSingleton.INSTANCE.showMessage();
    }
}

3.执⾏程序,输出结果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值