1.饿汉模式
//饿汉模式 public class Singleton1 { private static Singleton1 singleton1 = new Singleton1(); //饿汉嘛,很着急,所以在类加载时就进行初始化 private Singleton1() { } public static Singleton1 getSingleton1() { return singleton1; } }
饿汉模式是线程安全的,因为 private static Singleton1 singleton1 = new Singleton1(); 语句是在类加载时完成的,具体是在类加载的初始化阶段时的<client>方法中进行的。
2.懒汉模式
//懒汉模式 public class Singleton2 { private static Singleton2 singleton2; private Singleton2() { } public static synchronized Singleton2 getSingleton2() { //懒汉嘛,很懒,所以啥也不考虑,直接给方法上加synchronized来解决问题(并发时效率很低) if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }
这种懒汉模式是线程安全的,在这里只有获取到Class的对象锁才会进入到方法。
3.双重检测模式
//双重检测模式(可以理解为比懒汉勤快的一种模式。。。) public class Singleton3 { private static volatile Singleton3 singleton3; private Singleton3() { } public static Singleton3 getSingleton3() { //不在方法上加synchronized, if (singleton3 == null) { //先判断是否null,为null时才回去加锁 synchronized (Singleton3.class) { //这是只有在单例还没有被初始化时才需要进行加锁 if (singleton3 == null) { //再一次进行检测,这里主要是因为可能在对象还没初始化时,可能已经有若干个线程已经加入到监视器锁中了,所以这里还要进行一次检测,防止产生多个实例 singleton3 = new Singleton3(); } } } return singleton3; } }
(1)在使用volatile后为线程安全的,volatile用来禁止指令重排序
(2)为什么要使用volatile关键字
由于 singleton3 = new Singleton3();是分为三步来进行的
①为对象分配内存空间
②初始化对象
③将对象复制给引用
由于代码在执行过程可能出现指令重排序,那么执行步骤可能会变成①-③-②,那么当线程A完成①③操作,B线程进入方法判断不为空,将会获取到这个没有初始化完成的对象。
而使用volatile后禁止这三条指令的重排序。保证在singleton3在不为空是对象已经被初始化。
4.静态内部类
//静态内部类 public class Singleton4 { private Singleton4() { } private static class SingletonHolder { //在静态内部类中去初始化单例 private static Singleton4 singleton4 = new Singleton4(); } public static Singleton4 getSingleton4() { return SingletonHolder.singleton4; //通过静态内部类来获取 } }
(1).该实现方式是线程安全的。首先我们看看java中类的初始化时机
其中之一为:在调用类读取或设置一个类的静态字段的时候。
所以只有我们在第一次调用getSingleton4()方法时,SingletonHolder才会被初始化。
关于以上代码可在https://github.com/LiWangCai/blogRelated中获取