Java设计模式之单例模式

单例模式:保证一个类仅有一个实例。

java中的单例模式共有以下几种写法:

1. 饿汉模式

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton () { }

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

类加载时就进行实例化,由此避免了多线程的同步问题。但是如果始终都没有用到这个实例会造成内存的浪费。

2. 懒汉模式

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}

这种方式与“饿汉模式”的区别在于把实例化的时机由类加载延后到运行期调用静态方法getInstance时。虽然实现了实例的懒加载,节省了资源,但是也由此带来了多线程获取单一实例的同步问题。问题的根源就在于instance = new Singleton();这行代码不是原子操作,为了保证getInstance静态方法中代码的执行是完全同步的,我们可以使用同步锁synchronized,这也就引申出了线程安全的“懒汉模式”。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}

但是众所周知,同步锁的效率是比较低的,尤其是当instance不为null时,去同步代码块的执行就没有必要了。为此,我们可以在上面的代码进一步改进,这就是我们所熟知的双重检查模式(DCL)。

3. 双重检查模式

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}

这种模式把同步锁由之前的静态方法中下放到真正需要加锁的实例化代码块中。其中第一次对instance == null的判断是为了避免不必要的同步。第二次则是保证在实例化之前,instance一定不为null。

但是上面的代码依旧是线程不安全的,问题主要是出在多线程间共享实例的一致性(可见性)上,首先instance = new Singleton();这条语句实际上可以拆分成以下三个原子操作阶段:

(1)堆上实例化Singleton类;

(2)为instance变量分配内存空间;

(3)instance变量指向实例化的Singleton对象;

其次,java多线程之间从抽象的角度来看,线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程需要读或写的共享变量的副本。而在java中,共享变量实际上就是存储在堆内存中的实例域、静态域以及数组元素。

所以结合以上两点,假设线程A首先获得同步锁,开始对instance进行实例化和赋值,此时instance变量会被立即写入到线程A私有的本地内存中,同步锁释放。线程B立刻进入代码块,此时由于线程A中instance变量的实例化对于线程B是不可见的,因此会再进行一次实例化的操作。究其原因,就是instance在线程A中实例化完成后没有立刻“刷新”到主内存中,导致其他线程完全不知情。

想要解决这个问题也很简单,java中提供了volatile关键字,可以保证被声明实例的每一次改变都会被立即“刷新”到主内存中,使其改变对所有的线程都可见。修改后的DCL最终版本如下:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

        return instance;
    }
}

4. 静态内部类单例模式

public class Singleton {
    private Singleton() { }

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

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

这种方式非常的巧妙,只有在第一次调用getInstance方法时虚拟机才会加载SingletonBuilder 类并初始化instance,达到了使用时才加载的目的。而且instance声明为静态常量的形式也保证了其全局唯一,且是线程安全的。

基本上工作中,常用的单例模式就是后两种写法。只要记住这两种写法也就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值