单例模式的七种实现-Singleton(Java实现)
1. 饿汉式
实现代码:
public class Singleton { private Singleton() { } private static Singleton singleton = new Singleton(); public static Singleton getInstance() { return singleton; } }
验证一下:
public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); // true }
如果用反射, 是否仍然是单例:
结果是反射破坏了单例
public static void main(String[] args) throws Exception { // 自定义单例方法获取 Singleton s1 = Singleton.getInstance(); // 反射获取 Constructor constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton s2 = (Singleton) constructor.newInstance(); System.out.println(s1 == s2); //false }
2. 懒汉式
将上面的饿汉式改为懒汉式:
public class Singleton { private Singleton() { } private static Singleton singleton; public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
验证一下:
public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); // true }
不过这是一种线程不安全的单例实现.
我们在Singleton中加上sleep来模拟一下线程切换:
public class Singleton { private Singleton() { } private static Singleton singleton; public static Singleton getInstance() { if (singleton == null) { try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } return singleton; } }
验证一下线程不安全:
public class Main3 { private static LinkedBlockingQueue<Singleton> singletons = new LinkedBlockingQueue<>(); public static void main(String[] args) throws Exception{ ExecutorService threadPool = Executors.newFixedThreadPool(10); for(int i= 0;i<100;i++){ threadPool.execute(()->{ singletons.offer(Singleton.getInstance()); }); } Singleton basic = singletons.take(); while(basic==singletons.take()){ System.out.println("continue"); continue; } System.out.println("走到这里说明单例失败"); } }
3. 懒汉式+同步方法
public class Singleton { private Singleton() { } private static Singleton singleton; public synchronized static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
验证一下:
public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); // true }
由于是同步, 所以同步方法内不会出现多线程执行的情况.
4. 懒汉式+双重校验锁
因为上面那种会每次进时都会进行同步锁, 很浪费性能, 所以在加锁之间先进行校验
public class Singleton{ private Singleton() { } private static Singleton singleton; public static Singleton getInstance() { if (singleton==null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
验证一下性能:
能明显看出来性能差距...5千倍...
同步方法, 即直接在方法声明处加了Synchronize的情况:
public static void main(String[] args){ long start = System.currentTimeMillis(); for(int i= 0;i<999999999;i++){ Singleton s = Singleton.getInstance(); } System.out.println(System.currentTimeMillis()-start); }
双重校验锁:
public static void main(String[] args){ long start = System.currentTimeMillis(); for(int i= 0;i<999999999;i++){ Singleton s = Singleton.getInstance(); } System.out.println(System.currentTimeMillis()-start); }
5. 懒汉式+双重校验锁+防止指令重拍
看似简单的一段赋值语句:instance = new Singleton(); 其实JVM内部已经转换为多条指令:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象
可以看到指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化被排在了后面,在线程A初始化完成这段内存之前,线程B虽然进不去同步代码块,但是在同步代码块之前的判断就会发现instance不为空,此时线程B获得instance对象进行使用就可能发生错误。
加上volatile关键字:
public class Singleton { private Singleton() { } private volatile static Singleton singleton; public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
验证一下性能:
能明显看出来加了volatile后对性能的影响, 由之前的5, 变为了302...
性能下降了, 但是相比于上面的双重校验锁, 更保证了线程安全.
public static void main(String[] args){ long start = System.currentTimeMillis(); for(int i= 0;i<999999999;i++){ Singleton s = Singleton.getInstance(); } System.out.println(System.currentTimeMillis()-start); }
6. 静态内部类
这也是一种很好的实现方式, 不仅懒加载, 还保证了线程安全, 性能也很好, 实现起来也很简单
public class Singleton { private static class LazyHolder { private static final Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return LazyHolder.instance; } }
验证一下性能:
public static void main(String[] args){ long start = System.currentTimeMillis(); for(int i= 0;i<999999999;i++){ Singleton s = Singleton.getInstance(); } System.out.println(System.currentTimeMillis()-start); }
7. 枚举
个人对枚举类型的理解还有限, 有待学习....
public enum Singleton { INSTANCE; private String name; Singleton() { this.name = "king"; } public static Singleton getInstance() { return INSTANCE; } public String getName() { return this.name; } }
验证一下性能:
public static void main(String[] args){ long start = System.currentTimeMillis(); for(int i= 0;i<999999999;i++){ Singleton s = Singleton.getInstance(); } System.out.println(System.currentTimeMillis()-start); }
学如不及,犹恐失之