阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

单例

31次阅读
没有评论

共计 2262 个字符,预计需要花费 6 分钟才能阅读完成。

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。

因为这个类只有一个实例,因此,自然不能让调用方使用 new Xyz() 来创建实例了。所以,单例的构造方法必须是private,这样就防止了调用方自己创建实例,但是在类的内部,是可以用一个静态字段来引用唯一创建的实例的:

public class Singleton {// 静态字段引用唯一实例:
    private static final Singleton INSTANCE = new Singleton();

    // private 构造方法保证外部无法实例化:
    private Singleton() {}}

那么问题来了,外部调用方如何获得这个唯一实例?

答案是提供一个静态方法,直接返回实例:

public class Singleton {// 静态字段引用唯一实例:
    private static final Singleton INSTANCE = new Singleton();

    // 通过静态方法返回实例:
    public static Singleton getInstance() {return INSTANCE;
    }

    // private 构造方法保证外部无法实例化:
    private Singleton() {}}

或者直接把 static 变量暴露给外部:

public class Singleton {// 静态字段引用唯一实例:
    public static final Singleton INSTANCE = new Singleton();

    // private 构造方法保证外部无法实例化:
    private Singleton() {}}

所以,单例模式的实现方式很简单:

  1. 只有 private 构造方法,确保外部无法实例化;
  2. 通过 private static 变量持有唯一实例,保证全局唯一性;
  3. 通过 public static 方法返回此唯一实例,使外部调用方能获取到实例。

Java 标准库有一些类就是单例,例如 Runtime 这个类:

Runtime runtime = Runtime.getRuntime();

有些童鞋可能听说过延迟加载,即在调用方第一次调用 getInstance() 时才初始化全局唯一实例,类似这样:

public class Singleton {private static Singleton INSTANCE = null;

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

    private Singleton() {}}

遗憾的是,这种写法在多线程中是错误的,在竞争条件下会创建出多个实例。必须对整个方法进行加锁:

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

但加锁会严重影响并发性能。还有些童鞋听说过双重检查,类似这样:

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

然而,由于 Java 的内存模型,双重检查在这里不成立。要真正实现延迟加载,只能通过 Java 的 ClassLoader 机制完成。如果没有特殊的需求,使用 Singleton 模式的时候,最好不要延迟加载,这样会使代码更简单。

另一种实现 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 还避免了第一种方式实现 Singleton 的一个潜在问题:即序列化和反序列化会绕过普通类的 private 构造方法从而创建出多个实例,而枚举类就没有这个问题。

那我们什么时候应该用 Singleton 呢?实际上,很多程序,尤其是 Web 程序,大部分服务类都应该被视作 Singleton,如果全部按 Singleton 的写法写,会非常麻烦,所以,通常是通过约定让框架(例如 Spring)来实例化这些类,保证只有一个实例,调用方自觉通过框架获取实例而不是 new 操作符:

@Component // 表示一个单例组件
public class MyService {...}

因此,除非确有必要,否则 Singleton 模式一般以“约定”为主,不会刻意实现它。

练习

使用两种 Singleton 模式实现单例。

下载练习

小结

Singleton 模式是为了保证一个程序的运行期间,某个类有且只有一个全局唯一实例;

Singleton 模式既可以严格实现,也可以以约定的方式把普通类视作单例。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2262字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中