1.饿汉式加载
类加载时就创建
public class MaYun {
private static Mayun instance = new Mayun();
private static getInstance() {
return instance;
}
private MaYun() {
//MaYun诞生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
2.懒汉式加载
类使用时加载
public class MaYun {
private static MaYun instance = null;
private MaYun() {
//MaYun诞生要做的事情
}
public static synchronized MaYun getInstance() {
if (instance == null) {
instance = new MaYun();
}
return instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
3.懒汉式的线程安全版(双重检验锁)
public class MaYun {
private volatile static MaYun instance;
private MaYun (){}
public static MaYun getInstance() {
if (instance == null) {
synchronized (MaYun.class) {
if (instance == null) {
instance = new MaYun();
}
}
}
return instance;
}
}
在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
4.静态内部类(推荐)
public class MaYun {
private static class SigletonHolder {
private static final instance = new MaYun();
}
public static final getInstance() {
return SigletonHolder.instance;
}
private MaYun() {
//MaYun诞生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
Call:MaYun.getInstance().splitAlipay();
加载MaYun类时,在类的加载阶段把静态内部类加载了,也就是利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以这种方法非常推荐。
5.编写一个包含单个元素的枚举类型(极力推荐)
public enum MaYun {
himself; //定义一个枚举的元素,就代表MaYun的一个实例
private String anotherField;
MaYun() {
//MaYun诞生要做的事情
//这个方法也可以去掉。将构造时候需要做的事情放在instance赋值的时候:
/** himself = MaYun() {
* //MaYun诞生要做的事情
* }
**/
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
Call:MaYun.himself.splitAlipay();
我们来认真分析一下这5种方法:
- 1234方法把构造器声明成私有的,但是这种方法是不能抵抗反射机制的攻击的!如下:
Constructor<?> constructor = MaYun.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Elvis instance = (Elvis) constructor.newInstance();
依然可以创建一个对象!
解决的方法是添加计数器,大于1时抛出错误:
public class MaYun {
private static AtomicInteger count = new AtomicInteger(0);
private static final MaYun INSTANCE = new Elvis();
private MaYun() {
if(count > 0) {
throw new IllegalArgumentException("Cannot create Elvis twice");
}
count.incrementAndGet()
}
而方法5美剧方法可以防止反射攻击,当你试图通过反射去实例化一个枚举类型的时候会抛出IllegalArgumentException异常:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:416)
at org.effectivejava.examples.chapter02.item03.enumoration.MaYun.main(MaYun.java:21)
从这里看,方法5具有绝对的优势!
2.如果上面实现的Singleton是可以序列化的。那么方法1234加上implements Serializable只保证它可以序列化,为了保证反序列化的时候,实例还是Singleton,必须声明所有的实例域都是transient的,并且提供 readResolve方法,否则,每次反序列化都会生成新的实例。
但是方法5无偿提供了序列化机制,绝对防止多次实例化,又是绝对的优势!
这就是方法5值得极力推荐的的原因,同时jdk1.5以上才能使用的方法但是这种方法用的人太少了!!所以改变从你我开始!