参考
Efficetive Java page14
深入浅出单实例Singleton设计模式
如何正确地写出单例模式
Singleton 1.0版本
public class Singleton { private static Singleton instance =null; private Singleton() { System.out.println("Singleton 实例化"); } public static Singleton getInstance() { if(instance==null){ instance = new Singleton(); } return instance; } }
Singleton的特点
- 私有(private)的构造函数,表明这个类是不可能形成实例了。这主要是怕这个类会有多个实例。
- 即然这个类是不可能形成实例,那么,我们需要一个静态的方式让其形成实例:getInstance()。注意这个方法是在new自己,因为其可以访问私有的构造函数,所以他是可以保证实例被创建出来的。
- 在getInstance()中,先做判断是否已形成实例,如果已形成则直接返回,否则创建实例。
- 所形成的实例保存在自己类中的私有成员中。
- 我们取实例时,只需要使用Singleton.getInstance()就行了。
单线程下
测试代码:
public class Tests { public static void main(String[] args) { for(int i=1;i<=1000;i++) { object相同则hashcode一样 system.out.println(singleton.getinstance().hashcode()); } < pre>多线程下
测试代码:
public class Tests { public static void main(String[] args) { TestSingletonThread T[]=new TestSingletonThread[101]; for(int i=1;i<=100;i++) { t[i]="new" testsingletonthread(); t[i].start(); } class testsingletonthread extends thread @override public void run() system.out.println(singleton.getinstance().hashcode()); < pre>=1000;i++)>可以发现在多线程下会实例化多个实例
Singleton 1.1版本
因为在多线程下会实例化多个实例,所以加入synchronized关键字
public class Singleton { private static Singleton instance =null; private Singleton() { System.out.println("Singleton 实例化"); } public static synchronized Singleton getInstance() { if(instance==null){ instance = new Singleton(); } return instance; } }但是此时在多线程下,当已经实例化一份实例后,之后的线程再去获得实例应该是并行的,而加上synchronized关键字后变成并行 影响程序性能
因为自己电脑为4线程所以开4个线程去获取Singleton的实例,来展示性能差异
代码public class Tests { public static void main(String[] args) throws InterruptedException { TestSingletonThread T[]=new TestSingletonThread[5]; long start,end; start = System.nanoTime(); for(int i=1;i<=4;i++) { t[i]="new" testsingletonthread(); t[i].start(); } for(int i="1;i<=4;i++)" t[i].join(); end="System.nanoTime();" system.out.println("运行时间"+(end-start) 1000.0+"微秒"); class testsingletonthread extends thread @override public void run() system.out.println(singleton.getinstance().hashcode()); < pre>=100;i++)>不加synchronized关键字 但会实例化多个
加synchronized关键字 不会实例化多个 但会损耗性能
Singleton1.2版本
为了改善性能同时又不实例化多个Singleton实例,可以使用双重检测(Double-Check)
代码
public class Singleton { private static Singleton instance =null; private Singleton() { System.out.println("Singleton 实例化"); } public static Singleton getInstance() { if(instance==null){ synchronized(Singleton.class){ if(instance == null) instance = new Singleton(); } } return instance; } }说明
1. 第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。
2. 不然,我们就开始同步线程。
3. 第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。
但是,如果你认为这个版本大攻告成,你就错了。主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给 singleton 分配内存
- 调用 Singleton 的构造函数来初始化成员变量,形成实例
- 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
对此,我们只需要把singleton声明成 volatile 就可以了。下面是1.3版:
Singleton 1.3版本
代码
public class Singleton { private volatile static Singleton instance =null; private Singleton() { System.out.println("Singleton 实例化"); } public static Singleton getInstance() { if(instance==null){ synchronized(Singleton.class){ if(instance == null) instance = new Singleton(); } } return instance; } }使用 volatile 有两个功用:
1)这个变量不会在多个线程中存在复本,直接从内存读取。
2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。
但是,这个事情仅在Java 1.5版后有用,1.5版之前用这个变量也有问题,因为老版本的Java的内存模型是有缺陷的。
Singleton 1.4版本
这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { System.out.println("Singleton 实例化"); } public static Singleton getInstance() { return instance; } }但是,这种玩法的最大问题是——当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。
于是,这个可能会与我们想要的行为不一样,比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。
Singleton 1.5版本
静态内部类
public class Singleton { private static class SingletonHolder { private static final Singleton instance = new Singleton(); } private Singleton() { System.out.println("Singleton 实例化"); } public static final Singleton getInstance() { return SingletonHolder.instance; } }上面这种方式,仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
Singleton 1.6 枚举 Enum
public enum Singleton { INSTANCE; public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); } }我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
#其它问题(参考链接1)
Class Loader
序例化 (声明所有实例域都是瞬时的(transient)的 并提供一个readResolve方法
Effective Java 77条
多个Java虚拟机
volatile变量
关于代码重用
=4;i++)>