单例模式

什么是单例模式?

通过单例模式的方法创建的类在当前进程中只有一个实例,或者说在当前软件系统中只有一个实例,而且该类只提供一个取得对象实例的方法,一般重量级的类都会用到单例模式

饿汉式(静态常量式)

package com.jc.singleton.type1;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-01 22:53
**/
public class XXXFactory {

    private final static XXXFactory single = new XXXFactory();

    //防止外部new
    private XXXFactory(){

    }
    //只声明一个public方法进行获取单例
    public static XXXFactory  getInstance(){
        return single;
    }

    public static void main(String[] args) {
        XXXFactory instance = getInstance();
        XXXFactory instance1 = getInstance();
        System.out.println(instance == instance1? "一样的实例":"不是一样的实例");//true

    }

}

:实现起来比较简单,利用了类加载的特性,避免了线程安全问题
不好:因为他在类进行加载的时候就实例化了,所以他没有实现懒加载(“懒加载”又称为延迟加载,就是在开发过程中,程序启动的时候不立刻使用的资源先不加载,当程序运行中需要使用的时候再去加载它。),但是导致类加载的情况有很多种(获取类的静态变量,静态方法,加载子类时发现父类并没有加载等等),因此不能确定有其他的方式对类进行加载,所以他没有达到懒加载的效果。如果在使用这个类之前就对此类进行加载了,或者从始至终就没有使用到这个类,那么就造成了内存的浪费
结论:通过这种方式实现单例模式,可能造成内存的浪费


饿汉式(静态代码块式)

package com.jc.singleton.type1;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-01 22:53
**/
public class XXXFactory {

    private  static XXXFactory single;
    
    static {
        single = new XXXFactory();
    }
    //防止外部new
    private XXXFactory(){

    }
    //只声明一个public方法进行获取单例
    public static XXXFactory  getInstance(){
        return single;
    }

    public static void main(String[] args) {
        XXXFactory instance = getInstance();
        XXXFactory instance1 = getInstance();
        System.out.println(instance == instance1? "一样的实例":"不是一样的实例");//true

    }

}

也可以实现单例模式,优缺点和静态常量式相同。
但是和静态常量式有一丢丢不同,虽然都是利用了jvm的类加载机制,但是静态常量式是在编译的时候就进行分配内存了,而静态代码块则是在初始化阶段才进行分配内存


懒汉式

package com.jc.singleton.type1;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-02 12:42
**/
class XXXFactory2{

    private static XXXFactory2 single;
    //私有化 防止外部new
    private XXXFactory2(){

    }
    //在获取他实例的时候才进行加载 分配内存
    public static XXXFactory2 getInstance(){
        if (single == null){
            single = new XXXFactory2();
        }
        return single;
    }
}
public class SingletonTest2 {
    public static void main(String[] args) {
        XXXFactory2 instance = XXXFactory2.getInstance();
        XXXFactory2 instance2 = XXXFactory2.getInstance();

        System.out.println(instance == instance2);//true
    }
}

为了解决懒加载的问题,于是有了懒汉式,在获取他的实例的时候才进行加载为其分配内存,达到了懒加载的效果,也不会浪费内存。
不好:但是!如果是多线程模式的话,一个线程进入到if (single == null),还没来的及往下执行,另一个线程也进入到这个语句就会通过,这样的话,single就被多次实例化了,都保证不了单例了!


懒汉式(线程安全)

package com.jc.singleton.type1;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-02 12:42
**/
class XXXFactory2{

    private static XXXFactory2 single;
    //私有化 防止外部new
    private XXXFactory2(){

    }
    //在获取他实例的时候才进行加载 分配内存
    //改:加锁就ok了
    public synchronized static XXXFactory2 getInstance(){
        if (single == null){
            single = new XXXFactory2();
        }
        return single;
    }
}
public class SingletonTest2 {
    public static void main(String[] args) {
        XXXFactory2 instance = XXXFactory2.getInstance();
        XXXFactory2 instance2 = XXXFactory2.getInstance();

        System.out.println(instance == instance2);//true
    }
}

确实解决了之前的懒汉式的线程安全的问题。
不好但是!getInstance方法被上锁了,也就意味着如果多个进行想要获得实例,必须一个一个拿,所以它的效率太低了。
额。。。一个一个拿确实效率很低,那我们能不能在getInstance中给局部上锁,达到一种第一个线程进来后,不让别的线程进来,让第一个线程实例化single,其他线程在进来的时候,直接获取就好了呢?即大哥干完了,小弟们坐享其果呢?继续往下看。


双重检查

class XXXFactory2{

    private  static volatile XXXFactory2 single;
    //私有化 防止外部new
    private XXXFactory2(){

    }
    //在获取他实例的时候才进行加载 分配内存
    //改:加锁就ok了
    //再改:方法内部加锁
    public  static XXXFactory2 getInstance(){
        //大哥进来处理完后,小弟们直接就return了
        if (single == null){
            synchronized (XXXFactory2.class) {
                if (single == null)
                single = new XXXFactory2();
            }
        }

        return single;
    }
}

volatile关键字:会强制将修改的值立即写入主存;

大哥线程(第一个要获取单例的线程)是怎么处理的呢?

线程分好多种:
①方法外的,即还没进来的线程
②方法内,但是在第一条if(single == null) 之前的
③第一条 if(single == null) 和 synchronized (XXXFactory2.class) {之间的


具体怎么处理的? 在同一时刻,有好多线程进入到了这个方法内部,并都通过了if(single==null)这条语句,突然一个线程对大家说,我先进去看看怎么回事,你们先在这里等一等,于是大哥线程就进去了,发现single为null,于是他就new了,但是跟他一起进来的线程,还是会一个一个的进去,因为他们回不了头(代码不能向上执行),所以只能继续往前走,所以跟大哥线程同时进来的这些线程还是会有效率问题,但是,他们出去之后,后来的所有线程获取实例都是直接return single. 形象点说,第一批通过if(single == null)的线程们,为后边的所有线程都作出了贡献,准确的说,**是大哥线程 为这一批后边的所有线程作出了贡献**。

为哪些线程作出了贡献?
①还没进来的线程和还没想要获取单例的线程
②方法内,但是在第一条if(single == null)之前的。(因为有volatile,所以他们都会读取到single不null)

: 在方法内部锁住局部代码即可,这样既解决了线程安全的问题,又达到了懒加载的效果,还保证了效率。


静态内部类

package com.jc.singleton;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-02 13:47
**/
public class Singleton {

    private Singleton(){

    }
    //静态内部类
    private static class SingletonInstance{
        private static final Singleton single = new Singleton();
    }
    //获取单例的时候直接拿静态内部类中的single即可
    public static Singleton getInstance(){
        return SingletonInstance.single;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);//true
    }
}

静态内部类的特点时就是它的外部类被加载的时候他并不会被加载,他不会被加载也就意味着单例也不会马上被实例化,而是等到想要获取单例的时候,用到了静态内部类的属性,单例才会被实例化,达到了懒加载的效果。
类的静态属性只会在第一次加载类的时候初始化,,所以这里是利用了jvm的特性(加载类的时是线程安全的,别的线程无法进入)来帮我们避免了线程安全问题。
:懒加载的效果,线程安全,效率高。


枚举

package com.jc.singleton;/**
* @program: 设计模式
*
* @description: 
*
* @author: Mr.Wang
*
* @create: 2021-10-02 13:58
**/
public class SingleTest4 {

    public static void main(String[] args) {
        Singleton1 instance = Singleton1.INSTANCE;
        Singleton1 instance2 = Singleton1.INSTANCE;
        System.out.println(instance == instance2);//true
    }
}
enum Singleton1{
    INSTANCE;
}

也可以通过枚举来实现单例。
:可以保证线程安全,而且还能防止反序列化重新创建新的对象,效率高。

怎么防止的?
Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的INSTANCE属性输入到字节流中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。


在JDK源码中的应用
private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

典型的饿汉式。


单例模式的应用场景

需要频繁创建和销毁的对象,重量级对象,经常用到的对象,包括工具类对象,频繁访问数据库或文件的对象(如各种数据源,各种Factory)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值