Post

Singleton pattern

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
public final class Singleton {

    private byte[] data = new byte[1024];
    
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式,在类一开始初始化就立即把单例对象创建出来。instance是一个类变量,在类初始化过程中会被收集进<clinit>()中,<clinit>()保证百分百同步,不会在多线程的情况下被实例化两次。

但是instanceClassLoader加载后可能很长一段时间才被使用,instance实例所开辟的堆内存会驻留很久,要是用不到这个实例则会造成内存浪费。

懒汉式

Lazy initialization: if null then new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class Singleton {
    
    private byte[] data = new byte[1024];

    private static Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {

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

如果 new 一次 singleton 开销很大的时候,所以我们就采用懒汉式。但是在多线程环境下,会导致instance被实例化一次以上,不唯一。也就是说两个线程同时看到instance==null,这时候instance就会保证不了单例的唯一性了。

懒汉式 + 同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class Singleton {

    private byte[] data = new byte[1024];

    private static Singleton instance = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {

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

满足了懒加载,又能保证instance实例的唯一性,但是synchronized关键字天生的排他性导致了getInstance方法只能在同一时刻被一个线程访问,性能差。

Double-Check Lock(DCL)

It’s thread-safe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class Singleton {

    private byte[] data = new byte[1024];
    
    private static volatile Singleton instance = null;

    Connection conn;

    Socket socket;

    private Singleton() {
        this.conn; // initialize conn
        this.socket; // initialize socket
    }
    
    public static Singleton getInstance() {

        if(null == instance) {
            synchronized(Singleton.class) {         
                if (null == instance) { 
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } // end of getInstance
} // end Singleton

如果不加volatile关键字,在初始化instance创建实例后,另一个线程看到了instance != null即可返回并使用类的实例变量connsocket,但是会有一种情况发生就是这时候这俩还没创建完成,未完成初始化的实例调用其方法就会抛出空指针异常。

出现的原因可能是 JVM 运行时指令重排序和 Happens-Before 规则,极有可能时instance最先被实例化。不过加上volatile关键字就能防止这种重排序的方式。

Enum

1
2
3
4
5
6
7
8
public enum Singleton {
    
    private byte[] data = new byte[1024];
    
    INSTANCE;

    public static void whateverMethod() {}
}

使用枚举的方式实现单例模式是《Effective Java》作者力推的方式,在很多优秀的开源代码中经常可以看到使用枚举方式实现单例模式的(身影),举类型不允许被继承,同样是线程安全的且只能被实例化一次,但是枚举类型不能够懒加载,对Singleton主动使用,比如调用其中的静态方法则INSTANCE会立即得到实例化。

Holder 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class Singleton {

    private byte[] data = new byte[1024];

    private Singleton() {}

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

Singleton类中并没有instance的静态成员,而是将其放到了静态内部类Holder之中,因此在Singleton类的初始化过程中并不会创建Singleton的实例,Holder类中定义了Singleton的静态变量,并且直接进行了实例化,当Holder被主动引用的时候则会创建Singleton的实例,Singleton实例的创建过程在 Java 程序编译时期收集至<clinit>()方法中,该方法又是同步方法,同步方法可以保证内存的可见性、JVM指令的顺序性和原子性 Holder 方式的单例设计是最好的设计之一,也是目前使用比较广的设计之一。

综上改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Singleton {

    private byte[] date = new byte[1024];

    private Singleton() {}

    private enum EnumHolder {

        INSTANCE;
        private Singleton instance;

        EnumHolder() {
            this.instance = new Singleton();
        }

        private Singleton getSingleton() {
            return instance;
        }
    }

    public static Singleton getInstance() {

        return EnumHolder.INSTANCE.getInstance();
    }
}
This post is licensed under CC BY 4.0 by the author.