单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:
1.限制类实例化对象。即只能产生一个对象。
2.保证外部能够获取这个对象,否则对象创建将毫无意义
如要满足以上两个条件,可以将构造方法私有化,然后在类中提供一个静态方法获取类实例。代码如下
1 public class SingleTon { 2 3 private static final SingleTon single = new SingleTon(); 4 5 private SingleTon() { 6 7 } 8 9 /** 10 * 获取一个单例对象。 11 * @return 返回SingleTon对象。 12 */ 13 public static SingleTon getInstance() { 14 return single; 15 } 16 17 }
JVM加载SingleTon后,会对静态成员做默认初始化,此时new SingleTon()创建的对象会赋值给single,类只会被加载一次,即使多次调用getInstance方法,所返回的对象也不会改变。single字段在初始化的过程中,对象就创建了,所以以上案例的代码又称为饿汉式。从对象的生命周期来看,类一旦加载,对象会在堆中立即创建,会浪费内存空间,因此,又存在另外一种称为懒汉式的单例设计模式。代码如下:
1 public class SingleTonLazy { 2 3 private static SingleTonLazy single = null; 4 5 private SingleTonLazy() { 6 7 } 8 9 /** 10 * 获取一个单例对象。 11 * @return 返回SingleTon对象。 12 */ 13 public static SingleTonLazy getInstance() { 14 15 if (single == null) { 16 single = new SingleTonLazy(); 17 } 18 19 return single; 20 21 } 22 23 }
SingleTonLazy在被加载进方法区后,不会立即创建对象,而是直到getInstance方法被调用以后,对象才会被创建。这种方式可以节约内存空间,但是也存在着线程安全问题,当线程A执行到判断对象为null,此时线程B获得执行权,线程B判断对象为null,此时线程A重新获得执行权,创建对象,线程B恢复,继续创建对象。将代码修改如下,使用同步锁解决线程安全问题。
1 public class SingleTonLazy { 2 3 private static SingleTonLazy single = null; 4 5 private final static Lock lock = new ReentrantLock(); 6 7 private SingleTonLazy() { 8 9 } 10 11 /** 12 * 获取一个单例对象。 13 * @return 返回SingleTon对象。 14 */ 15 public static SingleTonLazy getInstance() { 16 17 if (single != null) { 18 return single; 19 } 20 21 lock.lock(); 22 if (single == null) { 23 single = new SingleTonLazy(); 24 } 25 lock.unlock(); 26 27 return single; 28 29 } 30 }
总结:饿汉式与懒汉式各有优缺点,但是相对来说,占用内存空间比让cpu判断锁的开销要小,所以饿汉式更使用一些。