天天看点

Java(设计模式01)单例模式.

一、单例模式

1. 理解(理解可参考 模式1懒汉模式)

就是定义一个static的new 自己的成员变量

其他类想使用单例模式的类就只需要调用static 的成员变量

即可不用new 新的实例,只有1个实例(而且还是自己创建)

所以叫做单例模式。

2. 优点

1.节约内存:在一个内存中只使用一个实例,避免平凡创建实例和垃圾回收销毁实例的内存开销

2.防止对资源的多重占用.

3.缺点

1.没有接口,不能继承,

2.和单一职责冲突,一个类应该只关注实现的逻辑,而不是关心外面如何实例化

4. 使用场景

1.要求生产唯一的序列号(优点2)

2.web中的计数器,不用每次刷新就在数据库里面加一次,使用单例模式先缓存起来

3.创建对象需要消耗的资源很多,比如,IO和数据库的连接

5. 注意事项,同步安全

1.getInstance() 方法中需要使用同步锁 synchronized() 锁起来,防止多线程同时进入导致 instance 被多次实例化

二、6种单例模式的代码

提示:

1.一般不建议使用第1种和第2种懒汉模式

2.建议使用第3种饿汉模式

3.只有在明确要求 lazy loading 效果时候才会使用第5种模式

4.如果涉及到反序列化创建对象,使用6种枚举模式

5.如果有其他特殊的需求,可以考虑 4种双检锁模式

1. (不推荐)懒汉式(线程不安全)

功能 是否实现
lazy
多线程
难度 草鸡简单
描述 因为没有加锁,synchronize 所以不能多线程,严格来讲不叫单例模式,工作中不常用。
优点 简单
缺点 不安全,而且无多线程,在多线程时候无法真实地单例

1.SingleObject.java

/**
 * 作用: 定义单例模式的
 */
public class SingleObject {
    //1.单例模式唯一的实例.的声明.(使用private封装,public的get)
    private static SingleObject instance ;

    //2.构造函数 private 很关键,其他类无法实例化,只能单例模式。
    private SingleObject() {
    }

    //3. get 唯一给外面的接口,就是 getInstance 实例化定义 并 返回实例.
    public static SingleObject getInstance() {
        //定义实例
        if(instance == null){
            instance = new SingleObject();
        }
        //返回实例
        return instance;
    }

    //4. 可供实例调用的方法.
    public void printSomeText() {
        System.out.println("单例模式: 调用方法printSomeText()成功 ");
    }
}

           

2.SingleObjectDemo.java

public class SingleObjectDemo {
    public static void main(String[] args) {
        //1. 通过单例模式唯一的getInstance()获取实例
        SingleObject instance = SingleObject.getInstance();
        //2. 使用获取的实例调用单例模式的方法》
        instance.printSomeText();
    }
}
           

Run:

单例模式: 调用方法printSomeText()成功

Process finished with exit code 0

2. (不推荐)单例模式(线程安全)

getInstance添加了一个线程安全锁synchronized

功能 是否实现
lazy
多线程
难度 简单
描述 具备很好的lazy loading 能在线程中很好的工作,但是效率低下,99%的情况需要不需要同步。
优点 第一次调用才初始化,避免内存浪费
缺点 必须加锁才能保证单例,加锁会影响效率,getInstance( )对性能影响不是很关键。

代码:

1.

区别于模式1懒汉模式

getInstance添加了一个线程安全锁synchronized

package 设计模式.单例模式.模式2安全懒汉模式_不推荐;

/**
* 作用: 定义单例模式的
* 区别于模式1懒汉模式
* getInstance添加了一个线程安全锁
* synchronized
*/
public class SingleObject {
   //1.单例模式唯一的实例.的声明.(使用private封装,public的get)
   private static SingleObject instance ;

   //2.构造函数 private 很关键,其他类无法实例化,只能单例模式。
   private SingleObject() {
   }

   //3. get 唯一给外面的接口,就是 getInstance 实例化定义 并 返回实例.
   public static synchronized SingleObject getInstance() {
       //定义实例
       if(instance == null){
           instance = new SingleObject();
       }
       //返回实例
       return instance;
   }

   //4. 可供实例调用的方法.
   public void printSomeText() {
       System.out.println("单例模式: 调用方法printSomeText()成功 ");
   }
}
           

3. 饿汉模式(常用)

new SingleObject() 写到成员变量里

功能 是否实现
lazy
多线程 是(靠classloader的装载,不用等到程序多线程使用所以避免多线程问题)
难度 简单
描述 常用但是容易产生垃圾对象。
优点 没有加锁,效率高
缺点 类加载就初始化,浪费内存
注解 饿汉基于 classloader模式,避免的多线程的安全问题,不过instance在类装载的时候就会实例化,所以点点浪费内存 没达到 lazy loading的效果,但是一个装载也浪费不了多少内存所以最常用

代码:

3. 就是把

new SingleObject() 写到成员变量里

1.

/**
 * 作用: 定义单例模式
 * 
 * 3. 就是把
 * new SingleObject() 写到成员变量里
 */
public class SingleObject {
    //1.在类装载的时候就定义好了实例内容
    private static SingleObject instance = new SingleObject();

    private SingleObject() {
    }
    public static synchronized SingleObject getInstance() {
        return instance;
    }

    public void printSomeText() {
        System.out.println("单例模式: 调用方法printSomeText()成功 ");
    }
}
           

4. 双检锁(double checked locking)

判断是否存在再进入线程锁再判断是否存在

功能 是否实现
lazy 是(和懒汉1,2一样在调用的时候才初始化)
多线程
难度 复杂
优点 双检锁,多线程下能安全且高性能
缺点 复杂
使用场景 getInstance()对程序的影响很大的时候

判断是否存在再进入线程锁再判断是否存在

/**
 * 作用: 定义单例模式
 *
 * 4. 就是把
 * 双检锁就是判断是否存在再进入线程锁再判断是否存在,
 * 保证不会多创建实例
 * 而且比较高效
 */
public class SingleObject {
    private static SingleObject instance ;

    private SingleObject() {
    }
    public static  SingleObject getInstance() {
        /**
         *  * 双检锁就是判断是否存在再进入线程锁再判断是否存在,
         *  * 保证不会多创建实例
         *  * 而且比较高效
         */
        if (instance == null) {
            synchronized (SingleObject.class){
                if (instance == null){
                    instance = new SingleObject();
                }
            }
        }
        return  instance;
    }

    public void printSomeText() {
        System.out.println("单例模式: 调用方法printSomeText()成功 ");
    }
}
           

5. 登记式(静态内部类)

5.定义静态内部类里成员变量初始化 实例

getInstance()返回静态内部类的静态成员变常量

功能 是否实现
lazy 是(和懒汉不同,这个在显示调用getInstance() 才会装载静态内部类)
多线程 是(装载静态内部类的classLoader机制,适用于多线程)
难度 复杂
使用场景 存在静态区域
描述 和双检锁一样的功效,安全且高效但是只适用于有静态域的情况,双检锁是getInstance() 时候实例化,登记类(静态内部类)是在显示调用时候装载静态static内部类StaticInnerClass{},完成的初始化。

代码:

1.

/**
 * 作用: 定义单例模式
 * 5.定义静态内部类里成员变量初始化 实例
 * getInstance()返回静态内部类的静态成员变常量
 */
public class SingleObject {
    //定义静态内部类里成员变量初始化 实例
    private static class StaticInnerClass {
        private static final SingleObject instance = new SingleObject();
    }

    private SingleObject() {
    }

    public static SingleObject getInstance() {
        //返回静态内部类的静态成员变常量
        return StaticInnerClass.instance;
    }

    public void printSomeText() {
        System.out.println("单例模式: 调用方法printSomeText()成功 ");
    }
}
           

6. 枚举

功能 是否实现
lazy
多线程 是(自动支持序列化机制,绝对防止被多次实例化)
难度 简单,但是生疏
使用场景 不适用反射 reflection attack 的场景
优点 (最佳方式但是不熟练)

代码:

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}