在java中,单例有很多种写法,面试时,手写代码环节,除了写算法题,有时候也会让手写单例模式,
这里记录一下单例的几种写法和优缺点。
-
1.初级写法
-
2.饿汉式
-
3.懒汉式
-
4.双锁检验
-
5.内部类
-
6.枚举
1.初级写法
1 package sheJiMoShe; 2 3 public class Singleton { 4 /* 5 * 缺点:只在单线程的情况下正常运行,在多线程的情况下,就会出问题。 6 * 例如:当两个线程同时运行到判断instance是否为空的if语句,并且instance确实没有创建好时, 7 * 那么两个线程都会创建一个实例。 8 */ 9 10 private static Singleton instance=null; 11 private Singleton(){ 12 13 } 14 public static Singleton getInstance(){ 15 if(instance==null){ 16 instance=new Singleton(); 17 } 18 return instance; 19 } 20 }
上面这种写法,在并发环境下,会出现多个实例。
2.饿汉式
饿汉式的特点是:类在加载时就直接初始化了实例。即使没用到,也会实例化。
1 package sheJiMoShe; 2 3 public class ESingleton { 4 5 //初试化静态的instance创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。 6 7 //缺点:没有lazy loading的效果,从而降低内存的使用率。 8 private static ESingleton instance=new ESingleton(); 9 private ESingleton(){ 10 11 } 12 public static ESingleton getInstance(){ 13 return instance; 14 } 15 }
3.懒汉式
懒汉式的特点是:用到这个实例时才去调用方法实例化。这个和2中的看起来是一样的,因为这个实例化方法加了synchronized ,这样安全一些。
1 package sheJiMoShe; 2 3 public class BSingleton { 4 //多线程的情况可以用。(懒汉式,不好) 5 private static BSingleton instance=null; 6 private BSingleton(){ 7 8 } 9 //整个方法锁住了,效率低 10 public static synchronized BSingleton getInstance(){ 11 if(instance==null){ 12 instance=new BSingleton(); 13 } 14 return instance; 15 } 16 }
4.双锁检验(可行)
双重非空判断,new对象前加一次锁。
volatile 关键字的使用可以解决一定程度上的多线程访问时出现的同步问题,保证一定程度的线程安全。如果整形的变量由volatile修饰,在其值发生改变后,
能够快速被其他线程可见,告知其他线程自己发生改变。
1 package sheJiMoShe; 2 3 public class SynchronizedSingleton { 4 5 private volatile static SynchronizedSingleton instance=null; 6 private SynchronizedSingleton(){ 7 8 } 9 /* 10 * 只是在实例为空时才进行同步创建 11 * 为什么做了两次null判断? 12 * A线程和B线程同时进入同步方法0 13 * 然后都在1位置处判断了实例为null 14 * 然后都进入同步块2中 15 * 然后A线程优先进入同步代码块2中(B线程也进入了)然后创建了实例 16 * 此时,如果没有3处的判断,那么A线程创建实例的同时,B线程也会创建一个实例, 17 * 所以需要两次null判断 18 * 19 */ 20 public static SynchronizedSingleton getInstance(){//0 21 if(instance==null){//1 22 synchronized(SynchronizedSingleton.class){//2 23 if(instance==null){//3 24 instance=new SynchronizedSingleton();//4 25 } 26 } 27 } 28 return instance; 29 } 30 }
评价:经过两次判定,第一次检测到实例为空时,增加同步,同步后再次检测到实例为空时,才创建对象实例。有效防止了在多线程环境下创建多个实例的问题。
5.内部类(推荐:高效)
1 package sheJiMoShe; 2 3 public class FSingleton { 4 //静态内部内。(建议使用) 5 /* 6 * :定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。 7 * 而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。 8 优点:达到了lazy loading的效果,即按需创建实例。 9 */ 10 private FSingleton(){ 11 12 } 13 private static class SingletonHolder{ 14 private final static FSingleton instance=new FSingleton(); 15 } 16 public static FSingleton getInstance(){ 17 return SingletonHolder.instance; 18 } 19 }
6.枚举(推荐:高效)
在《Effective Java》最后推荐了这样一个写法,简直有点颠覆,不仅超级简单,而且保证了线程安全。这里引用一下,此方法无偿提供了序列化机制,绝对防止多次实例化,及时面对复杂的序列化或者反射攻击。单元素枚举类型已经成为实现Singleton的最佳方法。
1 package sheJiMoShe; 2 3 public enum MySingleton { 4 5 INSTANCE; 6 }
探究
对枚举法实现的单例模式很不理解的话。这里需要深入理解的是两个点:
- 枚举类实现其实省略了
private
类型的构造函数 - 枚举类的域(field)其实是相应的enum类型的一个实例对象
对于第一点实际上enum内部是如下代码:
1 public enum MySingleton { 2 INSTANCE; 3 // 这里隐藏了一个空的私有构造方法 4 private MySingleton () {} 5 }
一个标准的enum单例模式,最优秀的写法还是实现接口的形式:
1 // 定义单例模式中需要完成的代码逻辑 2 public interface MySingleton2 { 3 void doSomething(); 4 } 5 6 public enum Singleton implements MySingleton2 { 7 INSTANCE { 8 @Override 9 public void doSomething() { 10 ...11 } 12 }; 13 14 public static MySingleton2 getInstance() { 15 return Singleton.INSTANCE; 16 } 17 } 18