单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
懒汉式是典型的时间换空间,延迟加载,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
它的特点是加载类的时候比较慢,但运行时获得对象的速度比较快。它从加载到应用结束会一直占用资源。程序初始化的时候初始化单例。
public class EagerSingleton { //饿汉单例模式 //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快 private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化 private EagerSingleton() { //私有构造函数 System.out.println("new EagerSingleton"); } //静态,不用同步(类加载时已初始化,不会有多线程的问题) public static EagerSingleton getInstance() { return instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 100; i++) { fixedThreadPool.submit(() -> { EagerSingleton instance = EagerSingleton.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms"); System.out.println("生成类数:"+setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:"+s); } }
结果
new EagerSingleton 耗时:73ms 生成类数:1 hashcode:1512308980
它的特点是运行时获得对象的速度比较慢,但加载类的时候比较快。它在整个应用的生命周期只有一部分时间在占用资源。
public class Singleton001 { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton001 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton001() { System.out.println("new Singleton001"); } /* 静态工程方法,创建实例 */ public static Singleton001 getInstance() { if (instance == null) { instance = new Singleton001(); } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } }
会有多线程问题。
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 100; i++) { fixedThreadPool.submit(() -> { Singleton001 instance = Singleton001.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms"); System.out.println("生成类数:"+setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:"+s); } }
输出
new Singleton001 new Singleton001 new Singleton001 new Singleton001 耗时:72ms 生成类数:4 hashcode:158094720 hashcode:1046297923 hashcode:1303744419 hashcode:1572373644
public class Singleton002 { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton002 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton002() { } /* 静态工程方法,创建实例 */ public static synchronized Singleton002 getInstance() { if (instance == null) { instance = new Singleton002(); } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } }
但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,
测试:
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 50; i++) { fixedThreadPool.submit(() -> { Singleton002 instance = Singleton002.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms"); System.out.println("生成类数:"+setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:"+s); } }
输出
new Singleton002 耗时:67ms 生成类数:1 hashcode:1052225082
这种实现方式的运行效率会很低。同步方法效率低。
事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
public class Singleton003 { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton003 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton003() { System.out.println("new Singleton003"); } /* 静态工程方法,创建实例 */ public static Singleton003 getInstance() { if (instance == null) { synchronized (Singleton003.class) { instance = new Singleton003();// } } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(500); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 500; i++) { fixedThreadPool.submit(() -> { Singleton003 instance = Singleton003.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms"); System.out.println("生成类数:"+setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:"+s); } }
输出
new Singleton003 new Singleton003 耗时:68ms 生成类数:2 hashcode:158094720 hashcode:1961726163
这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是却没能保住线程的安全性。
在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。
public class Singleton004 { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton004 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton004() { System.out.println("new Singleton004"); } /* 静态工程方法,创建实例 */ public static Singleton004 getInstance() { if (instance == null) { synchronized (Singleton004.class) { if (instance == null) { instance = new Singleton004(); } } } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 50; i++) { fixedThreadPool.submit(() -> { Singleton004 instance = Singleton004.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("生成类数:" + setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:" + s); } }
输出
new Singleton004 耗时:63ms 生成类数:1 hashcode:1052225082
public class Singleton005 { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ volatile private static Singleton005 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton005() { System.out.println("new Singleton005"); } /* 静态工程方法,创建实例 */ public static Singleton005 getInstance() { if (instance == null) { synchronized (Singleton005.class) { if (instance == null) { instance = new Singleton005(); } } } return instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 500; i++) { fixedThreadPool.submit(() -> { Singleton005 instance = Singleton005.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("生成类数:" + setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:" + s); } }
输出
new Singleton005 耗时:63ms 生成类数:1 hashcode:1961726163
public class Singleton006 { /* 私有构造方法,防止被实例化 */ private Singleton006() { System.out.println("new Singleton006"); } /* 静态工程方法,创建实例 */ public static Singleton006 getInstance() { return SingletonFactory.instance; } //内部类 private static class SingletonFactory { private static Singleton006 instance = new Singleton006(); } //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉 protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return SingletonFactory.instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 500; i++) { fixedThreadPool.submit(() -> { Singleton006 instance = Singleton006.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("生成类数:" + setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:" + s); } }
输出
new Singleton006 耗时:67ms 生成类数:1 hashcode:752816814
看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。
public class Singleton007 { private static Singleton007 instance = null; /* 私有构造方法,防止被实例化 */ private Singleton007() { System.out.println("new Singleton007"); } static { instance = new Singleton007(); } /* 静态工程方法,创建实例 */ public static Singleton007 getInstance() { return instance; } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 500; i++) { fixedThreadPool.submit(() -> { Singleton007 instance = Singleton007.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("生成类数:" + setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:" + s); } }
输出
new Singleton007 耗时:67ms 生成类数:1 hashcode:1046297923
看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用
public class Singleton008 { private enum MyEnumSingleton{ singletonFactory; private Singleton008 instance; private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化 instance = new Singleton008(); } public Singleton008 getInstance(){ return instance; } } /* 私有构造方法,防止被实例化 */ private Singleton008() { System.out.println("new Singleton008"); } public static Singleton008 getInstance(){ return MyEnumSingleton.singletonFactory.getInstance(); } }
测试
@Test public void getInstance() { long start = System.currentTimeMillis(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50); // Set<Integer> setObj = new HashSet<>(); //线程并发问题 ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet(); for (int i = 0; i < 500; i++) { fixedThreadPool.submit(() -> { Singleton008 instance = Singleton008.getInstance(); setObj.add(instance.hashCode()); }); } fixedThreadPool.shutdown(); System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms"); System.out.println("生成类数:" + setObj.size()); for (Integer s : setObj) { System.out.println("hashcode:" + s); } }
输出
new Singleton008 耗时:72ms 生成类数:1 hashcode:346401817
是
002-创建型-03-单例模式(Singleton)【7种】
原文:https://www.cnblogs.com/bjlhx/p/11142097.html