单例模式(详解)
饿汉模式
public class HungryMode { private static HungryMode hm = new HungryMode(); private HungryMode() { System.out.println("creat hungryMode Instance"); } public static HungryMode getInstance() { return hm; } public static void ceshi() { System.out.println("ceshi"); } public static void main(String[] args) { HungryMode.ceshi(); } }
饿汉式单例,就是一个私有的构造方法加一个私有的静态当前类实例对象和一个公有的静态获取实例方法组成由于类实例对象为静态变量,所以在加载类的时候我们就会创建类的实例对象,这样的话比较消耗内存,浪费性能。
懒汉模式
public class LazyMode { private static LazyMode aLazyMode; private LazyMode() { System.out.println("create LazyMode Instance"); } public static LazyMode getInstance() { if(aLazyMode==null) { aLazyMode = new LazyMode(); } return aLazyMode; } public static void main(String[] args) { LazyMode.getInstance(); } }
懒汉式在饿汉式的基础上做了改进,类实例对象做了懒加载,也就是所谓的延时加载,所以提升了一些性能。
多线程的单例
以上的写法在单线程的编程环境中没有什么问题,但是如果是多个线程使用上面的单例模式就会违背单例模式的设计原则,出现多个对象,简单的做法是在获取类实例的方法上加同步锁,并且给类实例对象加上volatile修饰符,volatile能保证对象的可见性,即在工作内存的内容更新能立即在主内存中可见。工作内存是线程独有的内存,主内存是所有线程共享的内存。还有一个作用是禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。这在单线程看起来没什么问题,然而一旦引入多线程,这种乱序就可能导致严重问题。volatile关键字就可以从语义上解决这个问题。因为同步锁的机制,多个线程获取类实例对象会排队等待获取锁,这样是没必要的,因为大多数情况下类实例对象都已经创建成功了,所以不用进入加锁的代码块,于是就可以再次改进上面的代码为双重校验的单例模式,如代码所示
/** * 多线程的单例模式,使用双重校验机制 */ public class DoubleCheckMode { private volatile static DoubleCheckMode sDoubleCheckMode ; public DoubleCheckMode() { System.out.println(" created " + getClass().getSimpleName()); } public static DoubleCheckMode getInstance() { if (sDoubleCheckMode == null) synchronized (DoubleCheckMode.class) { if (sDoubleCheckMode == null) { sDoubleCheckMode = new DoubleCheckMode(); } } return sDoubleCheckMode; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread() { @Override public void run() { super.run(); System.out.println("thread" + getId()); DoubleCheckMode.getInstance(); } }.start(); } } }
使用静态内部类实例单例
这种实现就是利用静态类只会加载一次的机制,使用静态内部类持有单例对象,达到单例的效果,直接上代码吧
/** * 静态内部类的方式实现单例,可以保证多线程的对象唯一性,还有提升性能,不用同步锁机制 */ public class InnerStaticMode { private static class SingleTonHolder { public static InnerStaticMode sInnerStaticMode = new InnerStaticMode(); } public static InnerStaticMode getInstance(){ return SingleTonHolder.sInnerStaticMode; } }
使用枚举实现单例
public enum EnumMode { INSTANCE; private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } }
总结
枚举实现单例,不推荐在Android平台使用,因为内存消耗会其他方式多一些,Android官方也不推荐枚举,Android平台推荐双重校验或者静态内部类单例,现在的Android开发环境jdk一般都大于1.5了。所以volatile的问题不必担心。Java平台开发的Effective Java一书中推荐使用枚举实现单例,可以保证效率,而且还能解决反序列化创建新对象的问题。