logo头像
博客-Leo

设计模式:单例

单例模式就是确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。

单例三要素

  • 私有的构造方法;
  • 指向自己实例的私有静态引用;
  • 以自己实例为返回值的静态的公有方法。

1.饿汉式

public class S {

    private static S singleton = new S();

    public static S getS() {
        return singleton;
    }

    private S() {}
}

类加载的方式是按需加载,且加载一次。

因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

2.懒汉式

public class S {

    private static S singleton;

    public static S getS() {
        if(singleton == null) {
            singleton = new S();
        }
        return singleton;
    }

    private S() {}
}

懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

在多线程环境下:由于饿汉式单例天生就是线程安全的,可以直接用于多线程而不会出现问题;但懒汉式单例本身是非线程安全的,因此会出现多个实例的情况。

3.synchronized同步单例

synchronized方法

public class S {

    private static S singleton;

    public static synchronized S getS() {
        if(singleton == null) {
            singleton = new S();
        }
        return singleton;
    }

    private S() {}
}

使用synchronized修饰getS()方法,保证了对临界资源的同步互斥访问,也就保证了单例。

但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。

synchronized代码块

public class S {

    private static S singleton;

    public static S getS() {
        synchronized(S.class) {
            if(singleton == null) {
                singleton = new S();
            }
        }
        return singleton;
    }

    private S() {}
}

但是这种实现方式的运行效率仍然比较低,事实上,和使用synchronized方法的版本相比,基本没有任何效率上的提高。

4.双重检查方式

public class S {
    //使用volatile关键字防止重排序,
    //因为 new S()是一个非原子操作,可能创建一个不完整的实例
    private static S singleton;

    public static S getS() {
        if(singleton == null) {
            synchronized(S.class) {
                //只需在第一次创建实例时才同步
                if(singleton == null) {
                    singleton = new S();
                }
            }
        }
        return singleton;
    }

    private S() {}
}

为了在保证单例的前提下提高运行效率,对singleton进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。

必须使用volatile关键字修饰单例引用。

5.静态内部类

public class S {

    // 私有内部类,按需加载,用时加载,也就是延迟加载
    private static class Holder {
        private static S singleton = new S();
    }

    public static S getS() {
        return Holder.singleton;
    }

    private S() {}
}

使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法,而且类加载是按需加载,只加载一次。

6.枚举

public enum S {
    INSTANCE;

    public static S getS() {
        return INSTANCE;
    }
}

线程安全、调用效率高、没有延时加载。并且具有天然的防止反射和反序列化漏洞

7.原子操作

public class S {
    private static final AtomicReference<S> INSTANCE = new AtomicReference<>();

    public static S instance() {
        for(; ; ) {
            S instance = INSTANCE.get();
            if(instance != null) {
                return instance;
            }
            instance = new S();
            if(INSTANCE.compareAndSet(null, instance)) {
                return instance;
            }
        }
    }

    private S() {}
}

利用原子引用AtomicReference的CAS操作,确保线程安全并且单例。

支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励

评论系统未开启,无法评论!