原创

设计模式-单例模式

设计模式是什么?

软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

为什么要学习设计模式(目的)?

  • 代码重用性
  • 可读性
  • 可扩展性
  • 可依靠性
  • 使程序呈现高内聚,低耦合的特性

单例模式

饿汉式

1.静态常量饿汉式

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

/**
 * 单例模式-饿汉式
 */
class Singleton {

    /**
     * 1.构造器私有化,外部能new
     */
    private Singleton() {

    }

    /**
     * 2.本类内部创建对象实例
     */
    private final static Singleton instance = new Singleton();

    /**
     * 3.提供一个公有的静态方法,返回实例对象
     * @return instance
     */
    public static Singleton getInstance() {
        return instance;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958
优缺点分析
  • 优点:
    1. 写法简单,在类加载的时候就完成了实例化。
    2. 避免了线程同步的问题。
  • 缺点:
    1. 在类加载的时候完成实例化,没有达到懒加载(Lazy Loading)的效果。
    2. 如果从始至终始终未使用过这个实例,则会造成内存的浪费。

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

2.静态代码块饿汉式

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

/**
 * 单例模式-饿汉式
 */
class Singleton {

    /**
     * 1.构造器私有化,外部能new
     */
    private Singleton() {

    }

    /**
     * 2.本类内部创建对象实例
     */
    private static Singleton instance;

    /**
     * 在静态代码块中创建单例对象
     */
    static {
        instance = new Singleton();
    }

    /**
     * 3.提供一个公有的静态方法,返回实例对象
     * @return instance
     */
    public static Singleton getInstance() {
        return instance;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958
优缺点分析
  • 优点:
    1. 写法简单,在类加载的时候就完成了实例化。
    2. 避免了线程同步的问题。
  • 缺点:
    1. 在类加载的时候完成实例化,没有达到懒加载(Lazy Loading)的效果。
    2. 如果从始至终始终未使用过这个实例,则会造成内存的浪费。

懒汉式

3.线程不安全懒汉式

public class Singleton3 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

    /**
     * 当前getInstance才创建单例对象,懒汉式
     *
     * @return singleton
     */
    public static Singleton getInstance() {
        // 线程不安全
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958
优缺点分析
  1. 起到了Lazy Loading的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了 if (singleton == null) 判断语句块,还未来得及执行,另一个线程也进入了这个if判断语句,则会产生两个实例。
  3. 实际开发中,不要使用这种方式。

4.线程安全,同步代码块方法懒汉式

public class Singleton4 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

    /**
     * 单例模式的懒汉式的线程安全方法,加入了同步代码块(synchronized关键字)。
     *
     * @return singleton
     */
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958
优缺点分析
  1. 解决了线程不安全的问题。

  2. 效率太低了,每个线程在想获得类的实例化时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面想获得该实例,直接return就行了。方法进行同步效率太低。

在实际开发中,不要使用这种方式。

5.线程不安全的方法,错误的同步代码块方法懒汉式

public class Singleton5 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        // 仍然会出现多个
        if (singleton == null) {
            // 虽然加了同步代码块,但是仍然无法实现。
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
true
instance1.hashCode:460141958
instance2.hashCode:460141958

切记!!!不要使用这种方式,线程不安全的!这是一种错误的写法!

6.双重检查方式

public class Singleton6 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

class Singleton {
    /**
     * volatile
     */
    private static volatile Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        // 1
        if (singleton == null) {
            // synchronized锁
            synchronized (Singleton.class) {
                // 2
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958

优缺点分析

  1. 线程安全。
  2. 延迟加载。
  3. 效率高。

推荐使用。

7.静态内部类方式

public class Singleton7 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());
    }
}

class Singleton {
    private Singleton() {

    }

    public static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958

优缺点分析

  1. 线程安全。
  2. 延迟加载。
  3. 效率高。

推荐使用。

8.枚举方式

public class Singleton8 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2);

        System.out.println("instance1.hashCode:" +instance1.hashCode());
        System.out.println("instance2.hashCode:" +instance2.hashCode());

        instance1.sayOk();
    }
}

enum Singleton {
    /**
     * INSTANCE
     */
    INSTANCE;
    public void sayOk() {
        System.out.println("OK~~~");
    }
}

运行结果:

true
instance1.hashCode:460141958
instance2.hashCode:460141958
OK~~~

优缺点分析

  1. 借助JDK1.5中添加的枚举来实现单例模式。不仅可以避免多线程同步问题,而且还能防止序列化重新创建新的对象。
  2. 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
  3. 推荐使用。

JDK源码中的单例模式-Runtime

Runtime源码中的单例模式,是饿汉式的,一上来就创建了。

 public class Runtime {
     private static Runtime currentRuntime = new Runtime();

     public static Runtime getRuntime() {
         return currentRuntime;
     }

     private Runtime() {}

     // ...
 }

单例模式总结

单例模式注意事项和细节说明:

  1. 单例模式保证了系统中内存只存在该类一个对象,节省了系统资源,对于一些频繁创建销毁的对象,可以使用单例模式。
  2. 当想实例化一个类的时候,必须要使用响应的获取对象的方法,而不是直接使用new。

单例模式使用场景:

  1. 需要频繁创建和销毁。
  2. 创建对象耗时过多或者消耗资源过多(即重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如:数据源、session工厂等)。
正文到此结束
本文目录