一:恶汉模式,类加载的时候就已经把对象初始化好了。(为了防止在其他类中使用new 操作符创建对象,要使用一个private 标记的 构造方法)
package single;
public class Singleton1 {
public static final Singleton1 singleton=new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance(){
return singleton;
}
}
二:懒汉模式(懒加载)
package single;
public class Singleton2 {
public static Singleton2 singleton2 = null;
private Singleton2() {
// TODO Auto-generated constructor stub
}
public static Singleton2 getInstance() {
if (singleton2 == null) {// 1
singleton2 = new Singleton2();//2
}
return singleton2;
}
}
这种懒汉模式会造成线程安全问题,比如假如有两个线程A,B,都执行到上面的第二步,那么就生成了两个对象,后执行第二步生成的对象替换掉了
先执行第二步生成的那个对象。怎么办呢,加锁啊。。
三:加上同步锁
package single;
public class Singleton3 {
public static Singleton3 singleton3 = null;
private Singleton3() {
// TODO Auto-generated constructor stub
}
public synchronized static Singleton3 getInstance() {
if (singleton3 == null) {// 1
singleton3 = new Singleton3();// 2
}
return singleton3;
}
}
上面到不会造成线程安全问题了。但是效率低啊,假设有10个线程,第一个线程执行方法的时候,后面的都得排队。。怎么办?换个方式加锁
四:同步锁2
package single;
public class Singleton4 {
public static Singleton4 singleton4=null;
private Singleton4() {
// TODO Auto-generated constructor stub
}
public static Singleton4 getInstance(){
if(singleton4==null){//1
synchronized(Singleton4.class){
singleton4 =new Singleton4();//2
}
}
return singleton4;
}
}
这个和三的列子差不多,虽然好点,但是仍然会造成线程并发问题,假如有10个线程执行了第一步,进行了null check,然后就会创建10个对象,怎么办呢,double-check- lock .
五:同步锁3
package single;
public class Singleton5 {
public static Singleton5 singleton5=null;
private Singleton5() {
// TODO Auto-generated constructor stub
}
public static Singleton5 getInstance(){
if(singleton5==null){//1
synchronized(Singleton5.class){
if(singleton5==null){//2
singleton5 =new Singleton5();//3
}
}
}
return singleton5;
}
}
怎么样呢?似乎是完美无缺啊。但是,仍然不行,因为第三步 singleton5=new Singleton5()并不是原子操作,他事实上分三步 (1),分配 memory,(2)把对象的引用 指派给memory (3) 初始化memory,假如有一个线程,执行完了同步块,但是却卡在了(3)步,那么其他线程拿到的就是一个没有被初始化完成的对象。囧。。。。。不过别担心,我们仍然有办法。
五:变量singleton5前加volatile 关键字(volatile关键字保证了可见性,但不保证原子性,可见性的意思就是,一个线程对一个资源的修改,另一个线程会得到通知。可以参考这篇blog)不过据说有些jvm并没有实现volatile规范。。。。。)
六:内部类方式。
如果想保证线程安全,不想要lazy加载的话,第一种最合适,但如果想要线程安全,又先要lazy加载的话,我们还可以使用内部类的方式。
package single;
public class Singleton6 {
private static class InnerClass {
public static final Singleton6 singleton6 = new Singleton6();
}
public static Singleton6 getInstance() {
return InnerClass.singleton6;
}
}
内部类在第一次被调用的时候才被初始化
七:最高端大气上档次的是enum枚举类的方式
package single;
public enum Singleton7 {
singleton7;
}
每一个枚举类的常量都是这个枚举类的一个实列。。。。。