Singleton模式 只有一个实例
1. Singleton模式
Singleton
模式的目的:
- 想确保任何情况下都绝对只有1个实例
- 想在程序上表现出“只存在一个实例”
2. 示例程序
2.1 Singleton类
public class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){
System.out.println("生成了一个实例。");
}
public static Singleton getInstance(){
return singleton;
}
}
Singleton
类的构造函数是private
的,这是为了禁止从Singleton
类外部调用构造函数。如果从Singleton
类意外的代码中调用构造函数new Singleton()
,就会出现编译错误。如果程序员十分小心,不会使用new
关键字生成实例,就不需要定义构造函数为private
,但是这样的话,这个模式也没有意义了。 Singleton
模式的作用在于可以确保在任何情况下都只能生成一个实例。
2.2 Main类
public class Main {
public static void main(String[] args) {
System.out.println("Start.");
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
if (obj1 == obj2){
System.out.println("obj1和obj2是相同的实例");
}else {
System.out.println("obj1和obj2是不同的实例");
}
System.out.println("End");
}
}
运行结果:

3. 拓展思路
3.1 为什么必须设置限制
设置限制其实就是为程序增加一项前提条件。档存在多个实例时,可能会产生意想不到的Bug。但是如果我们可以确保只有一个实例,就可以在这个前提条件下放心的编程了。
3.2 何时生成这个唯一的实例
在程序运行后,在第一次调用getInstance
方法时,Singleton
类会被初始化。也就是在这个时候,static
字段singleton
被初始化,生成了唯一的实例。
3.3 习题
某位开发人员编写了如下的Singleton
类,但是这并非严格的Singleton
模式。请问为什么?
public class Singleton {
private static Singleton singleton = null;
private Singleton(){
System.out.println("生成了一个实例");
}
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
答:这是因为在国歌线程几乎同时调用Singleton.getInstance方法时,可能会生成多个实例。
例:
Singleton.java
public class Singleton {
private static Singleton singleton = null;
private Singleton(){
System.out.println("生成了一个实例");
slowdown();
}
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
private void slowdown(){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
Main.java
public class Main extends Thread{
public static void main(String[] args) {
System.out.println("Start.");
new Main("A").start();
new Main("B").start();
new Main("C").start();
System.out.println("End");
}
public void run(){
Singleton obj = Singleton.getInstance();
System.out.println(getName() + ":obj = "+ obj);
}
public Main(String name){
super(name);
}
}
运行结果:

在以上代码中,如下条件判断时线程不安全的:
if (singleton == null){
singleton = new Singleton();
}
在使用singleton == null
判断第一个实例是否为null后,执行了下面的语句:
singleton = new Singleton();
但是在赋值之前,其他线程可能会执行singleton == null
的判断。
因此定义getInstance
方法为synchronized
方法后才是严谨的Singleton模式
public class Singleton {
private static Singleton singleton = null;
private Singleton(){
System.out.println("生成了一个实例");
slowdown();
}
public static synchronized Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
private void slowdown(){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
或者使用双重锁:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){
System.out.println("生成了一个实例");
slowdown();
}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
private void slowdown(){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
}
}
}
在双重锁中我们需要为singleton加volatile修饰,因为singleton = new Singleton()
可以拆解为3步:
1、分配内存
2、初始化对象
3、指向刚分配的地址
若发生了重排序,假设A线程执行了1,3,还没有执行2,B线程会直接返回还没初始化的instance了,volatile可以避免重排序。
参考《图解设计模式》【日】结城浩 著 杨文轩 译
网友评论