设计模式(3)-单例模式

简介

什么时候使用单例模式

  • 对于一个类来说,如果创建了两个对象或更多对象,程序会出错。
  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。

什么是单例模式

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

类型

对象创建型模式

要点

  • 只能有一个实例。
  • 必须自己创建自己的唯一实例。
  • 必须给整个系统提供这一实例。

从具体实现角度来说,就是以下三点:

  • 单例模式的类只提供私有的构造函数。
  • 类定义中含有一个该类的静态私有对象。
  • 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

实现

测试是否能实现单例模式的工具类

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSingleton {

    public static void main(String[] args) throws InterruptedException {

        final Set<String> set = Collections.synchronizedSet(new HashSet<String>());
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    Singleton singleton = Singleton.getInstance();
                    set.add(singleton.toString());
                }
            });
        }
        Thread.sleep(5000);
        System.out.println("一共创建了" + set.size() + "个实例");
        for (String str : set) {
            System.out.println(str);
        }
        executor.shutdown();
    }
}

懒汉式-线程不安全

描述:这种实现最大的问题就是不支持多线程,所以严格意义上它并不算单例模式。
是否多线程安全:否
优点:

  • 避免了饿汉式的那种在没有用到的情况下创建事例,资源利用率高,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。

缺点:

  • 不支持多线程

代码实例

public class Singleton {  
    private static Singleton instance;  

    private Singleton (){}  

    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}  

测试是否支持多线程

执行TestSingleton .java的main()方法几次,观察结果,发现确实不支持多线程

懒汉式-线程安全

描述:支持多线程,但效率低
是否多线程安全:是
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
代码实例

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

测试是否支持多线程

执行TestSingleton .java的main()几次,测试并观察结果,发现确实支持多线程

饿汉式

描述:支持多线程,这种方式比较常用,但容易产生垃圾对象。
是否多线程安全:是
优点

  • 没有加锁,执行效率会提高。
  • 在类加载的同时已经创建好一个静态对象,调用时反应速度快 。

缺点

  • 类加载时就初始化,浪费内存。

代码实例

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}  

测试是否支持多线程

执行TestSingleton .java的main()几次
测试并观察结果,发现确实支持多线程

双检锁/双重校验锁(DCL,即 double-checked locking)

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。
是否多线程安全:是
优点 :资源利用率高,不执行getInstance()就不被实例,可以执行该类其他静态方法
缺点 :第一次加载时反应不快,由于java内存模型一些原因偶尔失败
代码实例

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getInstance() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

测试是否支持多线程

执行TestSingleton .java的main()几次,测试并观察结果,发现确实支持多线程

登记式/静态内部类

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。
是否多线程安全:是
优点:资源利用率高,不执行getInstance()不被实例,可以执行该类其他静态方法。
缺点 :第一次加载时反应不够快。
代码实例

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
} 

测试是否支持多线程

执行TestSingleton .java的main()几次,测试并观察结果,发现确实支持多线程

枚举

描述:这种实现方式还没有被广泛采用,但据说这是实现单例模式的最佳方法。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。
是否多线程安全:是
优点:它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制。
缺点:枚举是JDK1.5以后才支持的。
代码实例

class Resource{
}

public enum Singleton{
    INSTANCE;
    private Resource instance;
    Singleton() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}  

上面的类Resource是需要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。
获取资源的方式很简单,只要 Singleton.INSTANCE.getInstance() 即可获得所要实例。

测试是否支持多线程

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSingleton {

    public static void main(String[] args) throws InterruptedException {

        final Set<String> set = Collections.synchronizedSet(new HashSet<String>());
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    Resource singleton = Singleton.INSTANCE.getInstance();
                    set.add(singleton.toString());
                }
            });
        }
        Thread.sleep(1000);
        System.out.println("一共创建了" + set.size() + "个实例");
        for (String str : set) {
            System.out.println(str);
        }
        executor.shutdown();
    }
}

执行TestSingleton .java的main()几次
测试并观察结果,发现确实支持多线程

总结

单例模式各种实现方法的优缺点

详见每种单例实现的介绍

优点

  • 只能创建一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

缺点

  • 扩展困难,因为单例模式没有抽象层。
  • 职责过重,在一定程度上违背了“单一职责原则”。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

注意事项
- 使用时不能用反射模式创建单例,否则会实例化一个新的对象。
- 使用懒单例模式时注意线程安全问题。
- 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式单例模式)。

饿汉式单例和懒汉式单例的不同

Tables饿汉式单例懒汉式单例
实例化对象时机单例类被加载时候调用实例方法时
是否支持多线程支持需要加synchronized
反应时间
资源利用率
延迟加载

适用环境

  • 资源共享的情况下,避免由于资源操作导致的性能或损耗。
  • 控制资源的情况下,方便资源之间的互相通信。

使用场景

  • Windows中的任务管理器
  • 日志
  • 数据库连接池
  • 线程池

单例模式的对象长时间不用会被jvm垃圾收集器收集吗?
待补充

问题

在一个JVM中会出现多个单例吗?

除非使用反射,否则在一个JVM中不会出现多个单例。

单例类可以被继承吗?
为了单例类在外部被实例化,构造方法被声明成了私有,所以单例类不可以被继承。因为子类会默认调用父类的构造方法,如果父类的构造方法是私有的,就会报错。

单例模式和static方式区别,该如何选择

区别:单例模式,归根结底还是创建了类的实例。而static方式不会创建任何实例,只是把类加载到了内存。
选择:如果只是想提供一个通用公共接口方法的话,就好比一些以Utils结尾的类,那就用static方式比较好,如果有更多事要做的话,比如管理一些状态,对象等,就用单例模式。

在软件开发中,你在哪里用到了单例模式?
面试的时候经常被问到,好好想想吧^^

本文已收录于专栏:24种设计模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值