单例模式:
在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例 。 这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需 要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让 类的实例去生成另一个唯一实例毫无意义
饿汉式:
//只有内部类可以为static。 public class SingleEH { //在自己的内部定义自己的一个实例,只供内部调用 private static final SingleEH instance = new SingleEH(); private SingleEH(){ } //这里提供了一个供外部访问本class的静态方法,可以直接访问 public static SingleEH getInstance(){ return instance; } }
懒汉式:
public class SingleLH { public static SingleLH instance = null; public static synchronized SingleLH getInstance(){ // 这个方法比上面有所改进,不用每次都进行生成对象,只是第一次 // 使用时生成实例,提高了效率! if (instance == null){ instance = new SingleLH(); } return instance; } }
多线程下的单例模式
上面介绍了一些单例模式的基本应用方法,但是上面所说的那些使用方式都是有一个隐含的前提,那就是他们都是应用在单线程条件下,一旦换成了多线程就有出错的风险。
如果在多线程的情况下,饿汉式不会出现问题,因为JVM只会加载一次单例类,但是懒汉式可能就会出现重复创建单例对象的问题。为什么会有这样的问题呢?因为懒汉式在创建单例时是 线程不安全的,多个线程可能会并发调用他的newInstance方法导致多个线程可能会创建多份相同的单例出来。
那有没有办法,使懒汉式的单利模式也是线程安全的呢?答案肯定是有的,就是使用加同步锁的方式去实现
懒汉式同步锁:
public class SingleLHS { private static SingleLHS instance = null; private SingleLHS(){ } public static SingleLHS getInstance() { synchronized (SingleLHS.class) { if (instance == null) { instance = new SingleLHS(); } } return instance; } }这种是最常见的解决同步问题的一种方式,使用同步锁synchronized (Singleton.class)防止多线程同时进入造成instance被多次实例化
双重校验锁
public class SingletSCS { private static volatile SingletSCS instance = null; private SingletSCS(){ } public static SingletSCS getInstance() { // if already inited, no need to get lock everytime if (instance == null) { synchronized (SingletSCS.class) { if (instance == null) { instance = new SingletSCS(); } } } return instance; } }可以看到上面在synchronized (Singleton.class)外又添加了一层if,这是为了在instance已经实例化后下次进入不必执行synchronized (Singleton.class)获取对象锁,从而提高性能。
以上两种方式还是挺麻烦的,我们不禁要问,有没有更好的实现方式呢?答案是肯定的。 我们可以利用JVM的类加载机制去实现。在很多情况下JVM已经为我们提供了同步控制,比如:
在static{}区块中初始化的数据
访问final字段时
等等
因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:
采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载和线程安全。
实现代码如下:
静态内部类
public class Singleton{
//内部类,在装载该内部类时才会去创建单利对象
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton newInstance(){
return SingletonHolder.instance;
}
public void doSomething(){
//do something
}
}
这样实现出来的单例类就是线程安全的,而且使用起来很简洁,麻麻再也不用担心我的单例不是单例了。
然而这还不是最简单的方式,Effective Java中推荐了一种更简洁方便的使用方式,就是使用枚举。
枚举类型单例模式
public enum Singleton{
//定义一个枚举的元素,它就是Singleton的一个实例
instance;
public void doSomething(){
// do something ...
}
}
使用方法如下:
public static void main(String[] args){
Singleton singleton = Singleton.instance;
singleton.doSomething();
}
默认枚举实例的创建是线程安全的.(创建枚举类的单例在JVM层面也是能保证线程安全的), 所以不需要担心线程安全的问题,所以理论上枚举类来实现单例模式是最简单的方式。
总结
一般单例模式包含了5种写法,分别是饿汉、懒汉、双重校验锁、静态内部类和枚举。相信看完之后你对单例模式有了充分的理解了,根据不同的场景选择最你最喜欢的一种单例模式吧!