1.1、介绍
单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。它解决了一个全局使用的类频繁地创建和销毁这一问题。
这个全局使用的类 在单例模式中需要创建自己的对象,同时确保只有单个对象被创建。并且这个类需要提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式特点有三:单例类只能有一个示例;单例类必须自己常见自己的唯一实例;单例类必须给所有其他对象提供这一实例。
这种类型的设计模式属于创建型模式。
下面介绍两种单例模式的实现:饿汉式、懒汉式。
1.2、饿汉式单例模式
该模式特点是:类一旦加载就创建一个单例,保证在调用 getInstance()
方法之前单例已经存在了。
public class Hungry {
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
// 只要是单例模式,一定要构造器私有
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
// 对外的方法
public static Hungry getInstance(){
return HUNGRY;
}
}
饿汉式单例 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
1.3、懒汉式单例模式
该模式特点是:类加载时没有生成单例,只有当第一次调用 getInstance()
方法时才去创建这个单例。
public class LazyMan {
// 只要是单例模式,一定要构造器私有
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
// 用的时候再加载对象,先放在这里
private volatile static LazyMan lazyMan;
// 对外的方法
public static LazyMan getInstance() {
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
懒汉式单例模式下,如果加载多线程程序,一定要使用关键字 volatile
和 synchronized
形成双重检测锁模式(DCL懒汉式),避免线程不安全问题。另外懒汉式单例也存在缺点:就是每次访问时都要同步,会影响性能,且消耗更多的资源。
1.4、优缺点
单例模式可以保证内存中只有一个实例,减少了内存的开销,可以避免资源的多重占用,并且由于单例模式设置了全局访问点,可以优化和共享资源的访问。
但同样存在缺点。
单例模式一般没有接口,难以扩展。(要想扩展,只能修改原来的代码,但这样违背了开闭原则)
在并发测试中,单例模式不利于代码调试。(因为在调试过程中,如果单例中的代码没有执行完,不能模拟生成一个新的对象)
单例模式的功能代码通常写在一个类中,如果功能设计不合理,那么很容易违背单一职责原则。
1.5、使用 Java 中的 enum
实现单例模式
另一种实现 Singleton 的方式是利用Java的 enum
,因为Java保证枚举类的每个枚举都是单例,所以我们只需要编写一个只有一个枚举的类即可:
public enum World {
// 唯一枚举:
INSTANCE;
private String name = "world";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
枚举类也完全可以像其他类那样定义自己的字段、方法,这样上面这个 World
类在调用方看来就可以这么用:
String name = World.INSTANCE.getName();
使用枚举实现Singleton还避免了饿汉式单例的一个潜在问题:即序列化和反序列化会绕过普通类的 private
构造方法从而创建出多个实例,而枚举类就没有这个问题。