单例模式 ——保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。
单例模式的实现方式很简单:
- 只有
private
构造方法,确保外部无法实例化; - 通过
private static
变量持有唯一实例,保证全局唯一性; - 通过
public static
方法返回此唯一实例,使外部调用方能获取到实例。
package com.learn.singleton;
public class Earth {
//静态字段引用唯一实例
private static final Earth instance=new Earth();
private String name="earth";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//private 构造方法保证外部无法实例化
private Earth(){
}
//通过静态方法返回实例
public static Earth getInstance(){
return instance;
}
}
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;
}
但是加锁会严重影响性能。
另一种实现Singleton的方式是利用Java的enum
,因为Java保证枚举类的每个枚举都是单例,所以我们只需要编写一个只有一个枚举的类即可:
package com.learn.singleton;
public enum World {
INSTANCE;
private String name="world";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.learn.singleton;
public class Test {
public static void main(String[] args) {
Earth earth=Earth.getInstance();
System.out.println(earth.getName());
World world=World.INSTANCE;
System.out.println(world.getName());
}
}
测试结果如下
实际上,很多程序,尤其是Web程序,大部分服务类都应该被视作Singleton,如果全部按Singleton的写法写,会非常麻烦,所以,通常是通过约定让框架(例如Spring)来实例化这些类,保证只有一个实例,调用方自觉通过框架获取实例而不是new
操作符:
@Component // 表示一个单例组件
public class MyService {
...
}
因此,除非确有必要,否则Singleton模式一般以“约定”为主,不会刻意实现它。